본문 바로가기
ORM

자바 퍼시스턴스 기술의 결정판 - JPA 활용하기

by 타블로 2008. 9. 18.

자바 퍼시스턴스 기술의 결정판 - JPA 활용하기 

그동안 말도 많고 탈도 많았던 EJB3.0에서 엔티티빈을 제치고 등장한 JPA는 그 변신의 내용과 성공여부에 대해서 많은 관심을 끌고 있다. JPA는 과연 우후숙준으로 등장한 자바의 퍼시스턴스 기술들 속에서 표준 퍼시스턴스 기술로서 자리매김을 할 수 있을 것인가. 아니면 엔티티빈이 그랬던 것처럼 현실과 동떨어진 이상적인 스펙의 실패사례로 또 한 번의 오명을 남길 것인가? 

이일민 tobyilee@gmail.com 

데이터베이스 프로그래밍으로 대표되는 퍼시스턴스 기술은 대부분의 데이터가 장기간 지속적으로 유지되어야 하는 오늘날의 애플리케이션에의 가장 중요한 역할을 담당하는 핵심기술임에 분명하다. 자바의 퍼시스턴스 기술은 자바 표준에 스펙을 정하고 그 구현을 각 DB벤더와 전문 업체들에게 맡기는 구조의 JDBC(Java Database Connectivity)라는 기술에서 출발했다. JDBC는 SQL 중심의 트랜잭션 스크립트 방식의 개발로는 안정적인 개발방식을 지원했기 때문에 자바가 데이터베이스를 사용하는 엔터프라이 시스템 개발에 빠르게 적용하게 되는 중요한 역할을 담당했다. JDBC는 그 이후 등장한 DataSource를 이용한 효과적인 connection 관리 기법의 적용과 JNDI 등을 통한 엔터프라이즈 리소스와의 연계 등에 힘입어 JDBC4.0에 이른 지금까지 모든 자바 퍼시스턴스 기술의 코어로서 성공적인 발전을 해왔다.

90년대 후반에 등장한 EJB 기술에서는 그러한 JDBC을 기반으로 한 트랜잭션 스트립트 방식을 탈피해서 자바 객체의 특성을 유지한 채로 퍼시스턴스 기술을 적용할 수 있는 엔티티빈이 소개되었다. 엔터프라이즈 환경의 컴포넌트 기술인 EJB자체도 주목을 받았지만 그 중에서도 엔티티빈은 자바 클래스로 만든 빈을 이용해서 데이터 처리를 손쉽게 할 수 있다는 면에서 큰 관심의 대상이 되었다. 
 

JPA가 등장하기 까지

EJB와 엔티티빈에 열광한 많은 개발자들이 엔티티빈 기술을 적극 홍보하고, 이를 활용한 각종 프레임워크를 만들어 사용하기 시작했다. 그 즈음에 본격적으로 발전하기 시작한 자바서버(WAS) 또한 빠르게 엔터프라이즈 환경에 적용되기 시작했고 많은 개발자들이 엔티티빈 기술을 애플리케이션의 퍼시스턴스 기술로 사용하려는 시도를 시작했다. 

하지만 막상 엔티티빈을 적용한 시스템은 이전에 SQL 스크립트를 사용한 JDBC 방식에 비해서 터무니없이 데이터베이스에 걸리는 부하에 놀랐고 아직은 성숙하지 못한 엔티티빈의 기술이 좀 더 발전하기를 기다려야만 했다. EJB 1.x 시절 엔티티빈의 미숙한 기술을 발전시키려는 노력이 자바서버 벤더와 커뮤니티 사이에서 많이 진행되었고 개발자가 JDBC 코드를 전혀 만들지 않더라도 매핑정보에 의해서 동작하는 발전된 CMP(Container Managed Persistence)기술로 무장한 EJB 2.0이 화려하게 등장했다. 기존 CMP에서 문제점으로 지적되었던 엔티티 사이의 관계부분도 CMR(Container Managed Relationship)을 이용해서 선언적으로 처리할 수 있다는 매력을 가진 기술이었다. 하지만 현장에서 요구되는 복잡한 퍼시스턴스 기능을 감당하기에 엔티티빈 2.0은 여전히 역부족이었다. 

엔티티빈은 EJB의 가장 대표적인 마케팅 포인트로 적극 활용되고 많은 관심을 일으킨데 반해서 현장에서는 가장 실망스러운 기술로 빠르게 그 인기가 떨어져버렸다. 그도 그럴 것이 한번이라도 엔티티빈을 적용해서 EJB 애플리케이션을 개발해 본 사람들이라면 사용법이 얼마나 복잡하고 불편하며, 현실과 동떨어진데다 무겁고 다루기 힘들다는 것을 느낄 수 있기 때문이다. 

CMP는 J2EE 기술 중 가장 복잡하고 많은 기술을 필요로 하는 것이었다. Full J2EE WAS의 라이센스 비용이 그토록 고가였던 이유 중의 하나도 CMP 기술에 투자되는 개발비와 그 연동 기술들 때문이었던 것을 생각하면 엔티티빈/CMP가 점차로 현장에서 사용되지 않게 되면서 고가의 WAS 라이센스와 유지보수 비용에 대한 부담이 결국 개발자들의 부담으로 돌아가는 악순환이 이어졌다. 그도 그럴 것이 EJB를 사용하면 엔티티빈 등을 사용할 수 있으므로 개발이 매우 쉽고, 생산성이 높다고 WAS 벤더들과 EJB 컨설턴트들이 열심히 홍보한 덕에 고객들과 기술을 잘 모르는 간부들은 적은 인원으로 더 빨리 개발할 수 있는 것을 기대하며 개발비용의 많은 부분을 고가의 엔티티빈을 지원하는 서버라이센스 비용에 투자했다. 그런데 막상 개발자들은 엔티티빈으로 고생하느니 세션빈에서 자체적으로 만든 퍼시스턴스 프레임워크를 써서 기존의 방법으로 개발해야 하니 사실상 기대한 것보다 더 많은 작업에 시간을 쏟아야 했다. 그 암울한 결과와 성능에 대한 비난은 고가의 최신기술을 사용하면서도 아웃풋을 못내는 개발자들이 고스란히 떠안아야 했다.

자바엔터프라이즈 개발자들의 엔티티빈에 대한 불만을 개선한 EJB2.1이 나온 후에도 개발자들의 냉대는 조금도 나아지지 않았다. 결국 2004년에 미국에서 조사된 바에 따르면 EJB 사용하는 엔터프라이즈 환경에서 CMP를 사용하는 비율이 1% 미만이라는 참담한 결과에 이르게 되었다. 

사실 EJB에서 엔티티빈의 도입은 EJB 스펙 개발에 참여한 한 벤더의 고집에 의한 것이라는 사실은 잘 알려져 있다. 결국 EJB 스펙의 주요 설계에 참여한 대형 벤더간의 엔터프라이즈 빈 기술에 대한 의견일치가 이루지지 않으므로 세션빈과 엔티티빈이라는 이중구조가 이루어졌고 결국은 각각의 역할에 충실하지 못한 어정쩡한 모습의 참담한 결과에 이르게 되었다.  

자바클래스로 만든 엔티티를 사용하는 퍼시스턴스 기술에 대한 비판은 사실 자바진영 안팎에서 오래전부터 있어왔다. 밖으로는 마이크로소프트(이하 MS)의 .NET 진영에서 자바의 엔티티빈의 무용성에 대해서 많은 비판과 함께 .NET의 퍼시스턴스 기술의 우위에 대해서 주장해왔다. 엔터프라이즈 시장에서 자바에 도전장을 낸 MS의 전략적인 공격이라는 면에서 MS의 주장은 이해한다고 해도, 자바 내부진영 특히 EJB를 핵심 기술로 내세우는 많은 벤더와 컨설팅 업체의 기술리더들 중에도 엔티티빈의 무용성에 대해서 주장하는 사람들이 줄 곳 있어왔다는 사실은 이미 초창기부터 엔티티빈 기술이 한계를 가지고 있었다는 증거가 아닐까 생각된다. 

 

엔티티빈의 대안 기술 

엔티티빈이 이렇게 현장에서 인정받지 못하는 동안에 대안으로 등장한 것들이 여럿 있다. 가장 대표적으로 사용된 방식은 세션빈을 이용해서 만든 자체적인 발전시킨(in-house) 퍼시스턴스 프레임워크들이다. 단순한 JDBC를 이용한 데이터 매퍼에서부터 나름 엔티티빈과 유사한 원시적인 ORM에 이르기까지 업체마다 제각각 퍼시스턴스 코드의 개발에 많은 투자를 하게 되었다. 일부 개발팀들은 상용기술 중에서 가장 인기 있었던 오라클의 톱링크(TopLink)와 같은 상용 퍼시스턴스 프레임워크를 사용해서 좋은 결과를 얻기도 했다. 한편으로는 다양한 오픈소스 퍼시스턴스 프레임워크가 등장했는데 그중 가장 빠르게 인기를 얻고 주목 받은 기술이 바로 POJO 기반의 퍼시스턴스 프레임워크를 모토로 하는 하이버네이트이다.  

● JDO 

또 한 가지 엔티티빈의 대안기술로 등장한 것은 JDO(Java Data Objects)이다. 인터페이스 기반의 퍼시스턴스 자바모델을 지향하는 JDO는 엔티티빈과 달리 엔터프라이즈 환경이 아닌 곳에서도 사용될 수 있는 자바 모델기반의 퍼시스턴스 기술로 많은 주목을 받았다. 하지만 역시 그 스펙의 이상주의적인 접근과 실질적인 한계 때문에 현장에서 그다지 사용되지 못하고 한 두 뛰어난 JDO 제품 벤더의 제품의 성공만으로 만족해야 했다. 

● 하이버네이트 

자바 퍼시스턴스 기술의 혼란함 속에서 가장 빛을 발한 것은 바로 하이버네이트의 성공이었다. 오픈소스라는 약점을 가지고 있음에도 불구하고 엔티티빈을 능가하는 퍼시스턴스 프레임워크를 만들어내겠다는 한 개발자의 뜨거운 열정과 그 매력에 빠져 깊이 있는 활동을 한 전 세계의 많은 개발자들로 구성된 커뮤니티는 하이버네이트를 엔티티빈이나 개발 업체의 퍼시스턴스 프레임워크의 훌륭한 대안으로 주목받게 만들었다.  

하이버네이트의 가장 큰 특징은 POJO 기반의 가벼운 프레임워크라는 점과 관계형 데이터베이스(RDB)와의 실제적인 연동기술에 주력을 했다는 점이다. 엔티티빈이 RDB를 비롯한 다양한 퍼시스턴스 기술을 포괄적으로 적용하려고 했기 때문에 관계형 데이터베이스의 특징을 잘 살려서 접근하지 못했던 것에 비해서 하이버네이트는 실제 퍼시스턴스 기술의 99%이상을 차지하는 관계형 테이터베이스에 주력해서 접근했기 때문에 그 기술자체가 매우 실용적인 특징을 띄고 있었다. 그래서 그 명칭 또한 객체-관계형디비-매핑(ORM)이라는 것을 사용했다. 또 POJO라는 반 EJB 캐치프레이즈를 모토로 삼아서 투명한 퍼시스턴스 기술을 지향했다는 점 또한 매력이었다. 굳이 고가의 복합한 자바엔터프라이즈 환경을 선택하지 않아도 어떠한 자바환경에서도 동작하는 POJO 기술이라는 것도 엔티티빈에 비해서 뛰어난 장점이었다. 동시에 세계적으로 매우 활발한 커뮤니티의 지원을 힘입어서 발전해온 오픈소스 기술이기 때문에 빠른 현장의 피드백과 아이디어들을 적용시키고 검증하는 과정을 통해서 점차 안정성을 인정받는 기술로 발전했다. 거기다 각종 캐싱 기술과 지능적인 상태관리 기술, 또 객체지향 기술의 장점을 잘 살린 매핑 기법 등이 ORM 처리의 오버헤드에 불구하고 JDBC로 직접 만든 코드에 비해서 10-20% 이내의 성능감소만 감당하면 훨씬 나은 생산성과 품질, 유지보수성을 가질 수 있는 매력적인 기술로 인정받게 되었다. 

 

EJB 3.0의 등장

그 즈음에 엔티티빈의 대안으로 오픈소스의 하이버네이트와 상용 퍼시스턴스 기술인 톱링크, 그리고 표준 기술인 JDO의 세 가지가 대두되면서 EJB 진영 내부에서 점차 반성의 목소리가 흘러나오기 시작했다. 가장 오랜 시간 심혈을 기울여서 만든 엔티티빈과 CMP기술 등이 시장에서 사장되다시피 했다. 여러 가지 대안 기술이 오히려 현장에서 발전하고 있는 동안 EJB의 명성을 다시 찾기 위해서 엔티티빈 기술을 대폭적으로 손대야 한다는 목소리가 크게 나왔다. 결국 EJB의 존속자체를 위협하는 위기를 탈출하기 위한 큰 수술을 감행하기로 결정한 EJB 설계팀은 EJB3.0을 EJB2.x 때와의 기술의 상당 부분을 포기하는 한이 있더라도 새로운 모델로 접근하기로 결정하고 새로운 전문가그룹(expert group)의 멤버를 영입했다. 초기 EJB 스펙을 설계하는 주도적인 역할에 참여한 사람이 바로 하이버네이트의 창시자이자 그 즈음에 JBoss팀에 차세대 JBoss 서버의 JDO 개발의 책임자로 영입된 개빈 킹(Gavin King)이다. 개빈 킹은 그의 하이버네이트를 통한 POJO 기반의 퍼시스턴스 프레임워크에 대한 기술적인 확신과 경험을 담아 EJB3.0의 엔티티빈 스펙을 완전히 새롭게 구성해 나가기 시작했다. 그에 따라 하이버네이트를 기반으로 한 JDO 솔루션을 준비하던 JBoss팀은 POJO 기반의 경량 엔터프라이즈 기술로 방향을 잡은 EJB3.0 스펙 개발에 지대한 역할을 하게 된다. 특히 개빈 킹이 만든 EJB3.0의 POJO 방식의 엔티티빈은 하이버네이트의 설계구조와 그 흐름이 유사한 모습으로 만들어졌다. 한편에서는 하이버네이트가 곧 EJB3.0이 되는 것이 아닌가 하는 경계의 목소리도 흘러나왔지만 엔티티빈 스펙의 설계에 함께 참여했던 JDO나 톱링크 커뮤니티에서도 POJO 방식의 퍼시스턴스 모델과 구조에 대해서 큰 이견이 있을 수 없었다. 이미 JDO나 톱링크도 그런 기술을 지향하고 있었기 때문에 그 초기 모델과 용어가 그 이후에 지속되어서 오늘까지 이르렀다. 

 

JPA의 독립 

그러는 사이 EJB3.0 스펙작업을 하고 있던 전문가 그룹으로부터 놀라운 소식이 발표되었는데 그것은 바로 엔티티빈/CMP파트를 독립적인 스펙으로 분리하겠다는 것이었다. 이에 대한 자바 개발자들의 반응은 매우 뜨거웠다. 이는 J2EE WAS를 구매할 때 CMP 엔진은 다른 벤더 제품을 쓸 수도 있다는 뜻이기 때문이다. 따라서 특정 벤더에 모든 것을 종속당할 필요가 없어진 것이다. 예를 들어 WebLogic 서버를 사용하면서 엔티티빈 엔진은 하이버네이트 기반의 CMP엔진을 써도 된다는 것을 뜻하는 것이다.  

결과적으로 EJB3.0 스펙은 두 가지로 구분이 되었다. 

첫째는 SessionBean과 MessageDriven Bean에 관한 것이고, 두 번째가 EJB3 Java Persistence API(JPA)이다. 

JPA는 스펙이 분리된 후 이름이 엔티티빈에서 JPA로 변경 되었는데, 그 이유는 JPA가 EJB 환경 밖에서도 사용될 수 있는 독립적인 퍼시스턴스 기술의 형태로 변모되었기 때문인 듯하다. 아직은 EJB3.0의 서브스펙으로 발표된 정도지만, 다음 버전의 JPA는 EJB에서 완전히 독립된 형태의 스펙으로 발표될 예정이다. 

결과적으로 JPA는 EJB 엔티티빈의 경량화와 개선에 머무른 것이 아니라 새로운 이름으로 자바의 모든 기술과 환경에서 사용할 수 있는 독립적인 POJO 기반의 퍼시스턴스 프레임워크로 새롭게 시작된 기술로 생각할 수 있다.

 

JPA의 구성요소 

JPA는 크게 여섯 가지의 구성요소로 구분해서 생각해 볼 수 있다. 도메인오브젝트 모델을 만드는 방법과 메타데이터 설정, API, 쿼리 작성방법, 라이프사이클 그리고 콜백에 관한 것으로 구성되어있다.

도메인 오브젝트 - 엔티티 

JPA의 핵심 구성요소는 바로 엔티티(entity)이다. 엔티티는 경량 퍼시스턴스 도메인 오브젝트라고 정의할 수 있다. ORM에서 말하는 Object 파트를 담당하는 클래스이다.

JPA의 엔티티는 기존 엔티티빈과 달리 특별한 슈퍼클래스를 상속하거나 인터페이스를 구현할 필요가 없다. 말 그대로 평범한 자바 오브젝트인 POJO 클래스로 만들면 된다.  

엔티티의 가장 큰 특징은 객체지향기술이 가지고 있는 특성을 그대로 다 사용할 수 있다는 것이다. 예를 들어 상속이나 다양성, 캡슐화 같은 것들을 그대로 적용하면서 퍼시스턴스 오브젝트로서의 사용할 수 있다. 이것이 JPA가 가지는 다른 퍼시스턴스 기술과의 가장 큰 차이점이자 매력일 것이다. POJO이므로 당연히 new 키워드에 의해서 생성되어 사용될 수 있을 것이다. 동시에 엔티티는 파라미터가 없는 생성자를 protected 이상의 접근조건으로 만들어야 한다. 

<리스트 1>은 id, name 필드와 임베디드 된 address필드 그리고 1:n관계에 있는 두 개의 orders, phones 정보를 가지는 엔티티의 예제다. 

모든 엔티티는 @Entity 어노테이션을 의무적으로 사용해야 한다. 이를 통해서 해당 클래스가 JPA 엔티티임을 선언하게 된다. Serializable을 마크 인터페이스를 사용한 이유는 detached 상태가 되었을 때 직렬화될 수 있다는 선언이다. 필요가 없다면 사용하지 않아도 된다.  

퍼시스턴스 필드의 정보는 두 가지 방법으로 사용할 수 있다. 하나는 인스턴스 변수를 사용하는 방법이다. 또 다른 하나는 getter/setter 방식의 자바빈 프로퍼티 방식으로 설정하는 것이다. 각각 사용상의 장단점이 있는데, 인스턴스 변수는 선언이 간단하고 비즈니스 메소드와의 혼동을 피할 수 있다는 장점이 있는 반면에 프로퍼티로 선언했을 때 가지는 정보의 은닉화와 로직을 구현할 수 없다는 등의 단점이 있다. 사용하는 개발자의 환경과 특성에 맞게 설정하면 된다. 

<리스트 1> 프로퍼티 액세스를 이용한 엔티티의 예

@Entity
public class Customer implements Serializable {
private Long id;
private String name;
private Address address;
private Collection<Order> orders = new HashSet();
private Set<PhoneNumber> phones = new HashSet();

public Customer() {
}

@Id
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

@OneToMany
public Collection<Order> getOrders() {
return orders;
}

public void setOrders(Collection<Order> orders) {
this.orders = orders;
}

@ManyToMany
public Set<PhoneNumber> getPhones() {
return phones;
}

public void setPhones(Set<PhoneNumber> phones) {
this.phones = phones;
}

// 비즈니스 메소드
public void addPhone(PhoneNumber phone) {
this.getPhones().add(phone);
phone.addCustomer(this);
}
}

 

● 퍼시스턴스 필드

JPA 엔진에 어떤 방식으로 퍼시스턴스 필드를 사용할지 알려주는 방법은 퍼시스턴스 어노테이션을 인스턴스 변수에 설정하는지, 프로퍼티 메소드에 설정하는지에 따라서 구분할 수 있다.

만약, @Id Long id; 라고 사용했다면 이것은 인스턴스 변수를 직접 액세스 하는 방식을 선택한 것이고, @Id public Long getId() { … } 이라고 정의했다면 프로퍼티 메소드를 통해 액세스하겠다는 선언이다.  

퍼시스턴스 필드를 선언할 때 주의 할 것은 @Id나 @OneTo Many처럼 반드시 퍼시스턴스 어노테이션이 필요한 필드 이외의 일반 스트링이나 숫자형 필드는 굳이 별다른 어노테이션을 넣지 않아도 자동으로 퍼시스턴스 필드로 인식한다는 점이다. EJB가 불필요할 정도로 방대한 설정파일 구조를 가져서 복잡한 툴의 도움 없이는 설정파일 하나 제대로 선언하기 힘들었던 방식 때문에 많은 비난을 들었던 것에 반해서 JPA는 지능적인 디폴트 설정을 최대한 활용해서 설정을 최소화 하면서도 꼭 필요한 메타정보를 삽입할 수 있는 구조로 만들어져 있음을 알 수 있다. 

다만 주의할 것은 모든 필드가 다 퍼시스턴스 필드가 아닐 수 있기 때문에 퍼시스턴스 필드나 프로퍼티가 아닌 것에는 반드시 @Transient 어노테이션을 삽입해줘야 한다. 

프로퍼티 방식의 선언인 경우 일반 비즈니스 메소드가 함께 사용될 수 있는데 이때 가능하면 getter/setter 방식의 명명법을 피하는 것이 좋다. JPA의 엔티티는 단지 데이터만 가지고 있는 반쪽짜리 오브젝트(anemic object)가 아니다. 따라서 얼마든지 엔티티에 종속적인 비즈니스 메소드를 삽입할 수 있다. 이때 단지 인스턴스 변수나 프로퍼티 액세스 메소드를 사용하는 것만으로 퍼시스턴스 데이터의 변화를 줄 수 있다. 이것이 바로 POJO 방식의 엔티티 구조가 가지는 최대의 장점이 아닐까 싶다. 


● 프라이머리 키
 

엔티티는 반드시 하나의 프라이머리 키 필드를 가지고 있어야 한다. 

일반적으로 권장되는 방식에 따라 프라이머리 키는 하나의 필드로 구성되는 것이 바람직할 것이다. 하지만 레거시 DB의 연동이나 필요에 따라 두 개 이상의 필드를 가지는 컴포짓 키(com posite key)를 사용할 수도 있다. 이때는 해당 필드들을 가지는 프라이머리 키 클래스를 만들어서 사용한다. 몇 가지 규약만 따른다면 어떤 형태의 컴포짓 키도 프라이머리 키로 표현해서 사용할 수 있다.  

프라이머리키의 선언은 @Id 어노테이션을 붙여주는 것으로 족하다.  

최근에는 프라이머리 키를 비즈니스 키로 선언하지 않고, 인조 키나 자동생성 키 방식을 사용하는 경우가 많다. Surrogate key 방식의 자동생성 키를 사용하는 것이 바람직한 이유는 많은 경우에 자연 키(natural key)나 비즈니스 키(business key)는 프라이머리 키로서의 특성을 깨뜨릴 수 있기 때문이다. 대표적으로 프라이머리 키의 값이 바뀔 수 있는 경우가 그에 해당된다. JPA 스펙에서도 프라이머리 키의 값은 바뀌면 안 된다고 명시되어있다. 또 컴포짓 키를 사용하는 경우 프라이머리 키의 크기가 커지고 그에 따라 조인이나 셀렉션 할 때 속도가 떨어지는 문제가 발생한다. 크기가 큰 키의 경우 인덱스 사용이 그다지 성능상의 장점을 가져오지 못한다는 것은 잘 알려져 있다. 

이에 따라 많은 데이터베이스가 지원하는 시퀀스나 아이덴티티를 이용한 방식의 surrogate key를 쓰는 것이 일반화되어있다. JPA는 이런 방식의 자동생성 키를 지원하기 위해서 @Gene ratedValue라는 특별한 어노테이션 선언을 지원한다. 

@Id
@GeneratedValue(strategy=SEQUENCE, generator=“CUST_ SEQ”)
public Long getId() { return id; } 

프라이머리 키 값의 자동생성은 AUTO, SEQUENCE, IDEN TITY, TABLE의 네 가지를 지원한다. SEQUENCE와 IDEN TITY는 데이터베이스가 지원하는 자동 값 생성 방식을 사용하는 것이다. TABLE은 유니크한 값을 생성할 수 있는 테이블을 이용하는 것이고, AUTO는 프로바이더가 자동으로 인식한 방식을 사용하는 것이다. 
 

JPA 메타데이터 

JPA의 메타데이터는 크게 두 가지로 나눌 수 있다. 하나는 도메인 오브젝트 모델의 논리적인 구성정보로서의 메타데이터이고, 다른 하나는 오브젝트 모델을 실제 데이터베이스 스키마에 어떻게 연동할 것인가를 다루는 매핑 메타데이터이다.  

메타데이터는 자바5 이상의 어노테이션을 사용할 수 있고 동시에 XML을 이용한 메타정보 설정도 가능하다.
 

● 논리 메타데이터 

<리스트 2>에 나와 있는 엔티티의 정의에 사용된 어노테이션들은 모두 도메인 오브젝트의 논리 메타 데이터들이다. 

<리스트 2> 논리 메타데이터의 예

@Entity
public class Order {
@Id
@GeneratedValue
private long pk;

@Version
private int oplock;

@ManyToOne
private Customer purchaser;

@OneToMany(cascade = CascadeType.PERSIST)
Set<Item> items = new HashSet<Item>();
// no annotation needed for simple types
private Date fulfillmentDate;

protected Order() {
} // for loading from db

public Order(Customer cust) {
purchaser = cust;
}

public void addLineItem(Item item) {
items.add(item);
}

public Set<Item> getLineItems() {
return items;
}
...
} 

@Entity는 앞에서 소개한 것처럼 해당 클래스가 JPA의 엔티티라는 선언이다. @Id와 @GeneratedValue는 해당 필드가 프라이머리 키로 사용되며 자동으로 생성되는 값을 가진다는 것이다. @Version으로 설정되어있는 필드는 옵티미스틱 락킹에 사용되는 필드로 버전을 이용한다는 선언이다. @OneToMany (cascade=CascadeType.PERSIST)는 해당 엔티티와 1:n 관계를 가진다는 선언이며 엔티티의 퍼시스트 작업이 일어날 때 그것이 그대로 전파된다는 선언이다. 이런 논리 메타데이터들은 실제 물리적인 데이터베이스에서 독립적이고 그 자체로 포터블한 성격을 가지기 때문에 일반적으로 어노테이션을 이용한 선언을 하는 것이 바람직하고 편리하다. 
 

● ORM 메타데이터 

ORM 메타데이터는 논리 메타데이터와 다르게 오브젝트와 실제 물리적인 데이터베이스의 스키마를 매핑시켜 주는 정보를 설정하는 것이다.
ORM 메타데이터는 엔티티의 PoC(Proofs-of concept)나 테스트를 위해서 어노테이션에 설정하는 것이 유용한 경우가 있다. 하지만 실제 프로젝트에 적용해서 사용하려면 XML에 설정하는 것이 바람직하다. 개발의 라이프사이클에 따라서 데이터베이스 스키마의 설정이 달라질 수 있기 때문이다. 개발과 테스트, QA, 운영 시스템에서의 각기 다른 스키마 정보와 데이터베이스의 타입 등을 매번 소스코드 내의 어노테이션을 변경해가면서 사용하는 것은 당연히 잘못된 접근이다. 따라서 빌드 시에 간단히 대치할 수 있는 독립된 XML을 이용하도록 하는 것이 좋다. XML설정은 기본적으로 어노테이션 설정을 오버라이드 한다.  

<리스트 3> 어노테이션을 이용한 ORM 메타데이터의 예

@Entity
@Table(name="PRODUCTS")
public class Product {
@Id @GeneratedValue
@Column(name="PRODUCT_PK")
private long id;

@Version int oplock;
private String name;

@ManyToOne
@JoinColumn(name="SUPP_FK",
referencedColumnName="SUPP_PK")
private Supplier supplier;
...
} 

@Table과 @Column은 대표적인 ORM 메타데이터 어노테이션이다. 일반적으로 이런 ORM 메타정보가 없을 경우 JPA는 디폴트로 해당 클래스 이름 또는 필드 이름을 테이블과 필드 이름으로 인식한다. 영리한 디폴트값의 대표적인 사례이다. 하지만 경우에 따라서 테이블이나 컬럼명이 다른 경우가 있으면 이때는 @Table나 @Column 등의 어노테이션을 사용해서 직접 지정할 수 있다. @Column에서는 컬럼의 크기와 상세한 DB필드 설정이나 제한 값 등도 정의할 수 있도록 되어있다. @JoinColumn은 외래 키(foreign key)의 설정을 줄 수 있다.  

<리스트 4> XML을 이용한 ORM 메타데이터 설정의 예

<entity-mappings version="1.0">
<entity class="com.example.jpa.Product">
<table>PRODUCTS</table>
<attributes>
<id name="id">
<generated-value />
<column>PRODUCT_PK</column>
</id>
<version name="oplock" />

<basic name="name">
<many-to-one name="supplier">
<join-column name="SUPP_FK"
referenced-column-name="SUPP_PK"/>
</many-to-one>
</attributes>
...
</entity>

<리스트 4>은 XML을 이용한 ORM 메타정보 설정의 방법이다. 어노테이션을 이용한 것과 거의 동일한 설정을 사용한다. 단지 XML로 표현되는 것이 다를 뿐이다. 


Java Persistence API
 

JPA의 API는 javax.persistence 패키지 안에 정의되어 있다. API의 핵심 인터페이스는 EntityManager와 Query 두 가지다. 


● EntityManager
 

EntityManager를 생성하는 방법은 팩토리를 이용하는 것과 JEE환경에서 JNDI를 에서 의존삽입을 통해서 얻는 두 가지 방법이 있다. 

<리스트 5> JNDI의 의존삽입을 통한 EntityManager 사용 예

@Stateless
@Remote(OrderManager.class)
public OrderManagerImpl implements OrderManager {
@PersistenceContext private EntityManager em;

public Order newOrderForProduct(long cId, long pId) {
Customer c = em.find(Customer.class, cId);
Product p = em.find(Product.class, prodId);
Order o = new Order(c);
em.persist(o);
o.addLineItem(new Item(p));
...
} 

자바 엔터프라이즈 환경에서 EJB를 사용한다면 다음과 같은 방법으로 현재의 활동 트랜잭션과 연계되는 EntityManager를 사용할 수 있다. 

자바 엔터프라이즈 환경에서 EJB를 사용한다면 다음과 같은 방법으로 현재의 활동 트랜잭션과 연계되는 EntityManager를 사용할 수 있다. 

JEE환경 밖이라면 애플리케이션이 관리하는 컨텍스트 안에서 팩토리를 이용해서 EntityManager를 가져 올 수 있다. 이때는 javax.persistence.Persistence 클래스로부터 시작해서 생성하면 된다. 

EntityManagerFactory emf = javax.persistence.Persistence.create EntityManagerFactory(“Order”);
EntityManager em = emf.createEntityManager(); 

JEE 환경 밖이라면 애플리케이션이 관리하는 콘텍스트 안에서 팩토리를 이용해서 EntityManager를 가져 올 수 있다. 이때는 javax.persistence.Persistence 클래스로부터 시작해서 생성하면 된다. 


JPQL
 

JPQL은 EJB2.x에서 사용하던 EJBQL2와 호환이 되는 JPA의 Query Langauge이다.  

JPQL은 엔티티 레벨의 고급 쿼리 설정을 지원한다. 또한 동적 쿼리뿐만 아니라 NamedQuery 방식의 정적 쿼리도 지원한다. 오브젝트 구조의 쿼리란 점을 제외하면 SQL과 비슷한 문법을 가지기 때문에 배우기가 쉽다. 그러면서도 메타정보에 정의된 관계정보를 이용하면 복잡한 Join 설정 등이 필요하지 않기 때문에 훨씬 간결하다. 다형성을 이용한 쿼리와 같이 SQL에서는 표현해 내기 힘든 복잡한 쿼리도 간편하게 정의할 수 있다는 장점도 있다. 만약 특별한 이유 때문에 native SQL을 사용해야 한다면 이를 사용할 수 있는 기능도 제공하는 등 매우 유연한 쿼리를 작성할 수 있다. 

<리스트 6> JPSQL의 예

Query q = em.createQuery("select c from Customer c where c.firstName = :fname order by c.lastName");
q.setParameter("fname", "철수");
q.setFirstResult(20);
q.setMaxResults(10);
List<Customer> customers = (List<Customer>)q.getResultList(); 

<리스트 6>은 “철수”라는 이름을 가진 사람을 성으로 정렬해서 그 중 20번째부터 30번째 사이의 사람 목록을 Customer 엔티티에 담아서 돌려주는 쿼리다. 

다음은 네이티브 SQL를 사용한 예이다. DB에서 사용할 수 있는 임의의 쿼리를 생성하고 이를 다시 엔티티의 리스트로 받을 수 있다.

Query q = em.createNativeQuery(“SELECT ID, VERSION, SUBCLASS, FIRSTNAME, LASTNAME FROM PERSON”, Person. class);
List<Person> people = (List<Person>) q.getResultList(); 

다음과 같은 bulk update/delete도 지원한다. ORM을 이용하면 대용량의 데이터의 수정을 위해서 매 레코드를 하나씩 수정해야 한다는 것은 잘못 알려진 사실이다. 단 하나 주의할 것은 벌크 업데이트나 삭제는 단일 레코드에 대한 작업용으로 사용해서는 안 된다는 것이다.

Query q = em.createQuery(“update Employee emp set emp.salary = emp.salary * 1.10”);
int updateCount = q.executeUpdate();
Query q = em.createQuery(“delete from Order o where o.fulfilledDate is not null”);
int deleteCount = q.executeUpdate();
 

Optimistic Locking 

낙관적 락킹이라고 불리는 옵티미스틱 락킹(optimistic locking)은 사용자 트랜잭션이 긴 웹 기반의 애플리케이션에서 매우 유용한 락킹 기법의 하나이다. JDBC 기반의 자체적인 프레임워크로 이를 구현하는 것은 대단히 복잡하고 어려운 작업이다. 이를 JPA에서는 간단한 어노테이션 설정 하나로 손쉽게 적용할 수 있도록 지원해준다. 

낙관적 락킹의 원리는 간단하다. 해당 레코드를 가져올 때의 버전이 후에 수정할 때 버전과 일치하면 그대로 커밋하고 그렇지 않으면 다른 사용자에 의해서 먼저 수정 되었다고 판단하고 예외를 발생시킨다.  

낙관적 락킹의 체크를 위해서는 일반적으로 정수 값을 사용하거나 타임스탬프를 이용한다. 

@Entity
public class Order {
@Id @GeneratedValue private long pk;
@Version private int oplock;
...
} 

그렇다면 비관적 락킹(pessimistic locking)은 어떨까? 비관적 락킹은 데이터베이스 벤더와 많은 JPA 구현에 의해서 지원되고 있지만 현재 JPA 스펙에서는 이에 대한 언급이 없다. 이 부분은 다음 버전의 JPA 스펙에 등장할 것으로 기대하고 있다.
 

성능 캐싱 

JPA의 스펙은 성능 캐싱에 대해서 정의하고 있지 않다. 다만 락킹이나 고립화에 대한 부분이 캐싱이 있다면 그에 따라 영향을 받아야 한다는 정도만 언급하고 있다. 스펙에 캐싱에 대한 상세한 정의를 넣지 않은 이유는 성능 캐싱 기술의 발전이 지금도 계속 되고 있고 그 혁신에 대해서 스펙이 제한을 가하지 않고 오픈되어있어야 한다는 의도라고 한다. 물론 대부분의 JPA 구현 기술은 어떤 방식으로든 성능 캐싱을 지원한다. 다만 그것이 표준이 아니라는 점에서 포터블하지 않다는 것이 아쉬울 따름이다.
 

JPA의 미래 

여전히 ORM의 세계에 발을 들여놓지 못하고 SQL 편집의 노가다에 몰두하는 개발자들이라면 ORM 세계의 즐거움을 JPA를 통해서 맛볼 수 있게 되기를 바란다. 지난 몇 년간 JPA나 그의 배경이 된 각종 ORM 기술을 현장에서 적용해오면서 많은 개발자들이 “ORM이 없던 시절에는 어떻게 애플리케이션을 개발했는지 상상이 안돼요”라는 이야기를 하는 것을 수도 없이 들었다. 초기 학습장벽이 높아서라는 핑계를 대고 안주하고 싶은 사람이 있다면 일본의 한 자동차 회사의 회장이 했다는 말을 들려주고 싶다. “우리는 처음부터 어려운 길을 택했습니다. 그래서 나중에 쉬워졌지요.” 


제공 : DB포탈사이트 DBguide.net