spring로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

spring로 검색한 결과
등록일:2008-03-20 10:31:12
작성자:
제목:POJO로 돌아온「EJB 3.0과 자바 퍼시스턴스 API」


EJB(Enterprise JavaBeans)는 한마디로 말해 분산 컴포넌트와 DB에 대한 접근 방법을 제공하는 기술이라고 할 수 있다.

EJB는 자바EE 프레임워크에서 백엔드를 담당하는 중요한 역할을 해왔지만 기존 EJB 2.1은 개발자에게 다가가기 쉬운 존재는 아니었다. 일반적인 자바 OOP 프로그래밍과는 다른 복잡한 구조와 많은 코드, 복잡한 XML 정의가 필요한 등의 문제 탓이었다. EJB 프로그래밍을 위해서는 많은 노력이 필요 했고, 당연히 처음 접하는 사람들에게는 높은 벽으로 느껴질 수 밖에 없었다.

하지만 EJB 3.0에서는 EJB만의 복잡한 프로그래밍 모델에서 탈피하여 일반적인 자바 프로그래밍 모델인 POJO(Plain Old Java Object) 스타일로 바뀌면서 단순화된 프로그래밍이 가능 해졌다.

POJO 스타일 프로그래밍은 이미 스프링(spring)이나 하이버 네이트(Hibernate)와 같은 오픈 소스 프레임워크에서 많이 사용 되고 있다. 단순화된 프로그래밍 모델과 코드의 재활용 가능성을 높여줄 뿐만 아니라 IDE의 리팩토링 기능을 십분 활용할 수 있다는 점들이 이 프레임워크들의 인기 비결이다.

또한 자바SE 5에서 추가된 메타데이터 어노테이션(Metadata annotation) 언어 기능을 사용하여 EJB에 관련된 설정을 XML 디스크립터(deployment descriptor)대신 코드 상에서 직접 할 수 있게 되었다. 이렇게 하면서 개발자는 복잡한 XML 설정에서 탈피할 수 있게 되고 코드 상에서 연관된 설정을 할 수 있기 때문에 코드의 가독성도 높아지게 되었다. 물론 그 외에도 여러 방면에서 개발자 편의성을 높이기 위한 노력이 이루어졌다.

자바 퍼시스턴스(Java Persistence) API 1.0은 DB에 접근하기 위해 사용되었던 기존 엔티티 빈을 대체하는 새로운 기술이다. 자바 퍼시스턴스는 엔티티 빈과는 아주 다른 POJO 기반의 ORM(Object-Relational Mapping) 프로그래밍 모델을 제공하며 기존에 존재하던 하이버네이트(Hibernate)와 같은 ORM 솔루션과 유사하다. 또한 EJB에 국한되지 않은 범용적인 기술로 만들어졌기 때문에 자바EE 환경에서뿐만 아니라 자바SE 환경 에서도 사용할 수 있다.

자바 퍼시스턴스는 EJB 3.0과는 별도로 다루기로 하고 여기에서는 먼저 EJB 3.0에 대해서 알아보자.

  EJB 3.0

EJB 3.0 프로그래밍 모델이 EJB 2.1에 비해서 얼마나 개선되었을까? 백문이 불여일견이라 했으니 Hello 메시지를 출력하는 간단한 예제 코드인 HelloEJB를 통해 살펴보도록 하자. <리스트 1>은 기존 EJB 2.1 방식으로 구현한 것이다.


 <리스트 1> EJB 2.1로 구현한 HelloEJB와 클라이언트

// 컴포넌트 인터페이스
public interface Hello extends EJBObject {public String sayHello() throws RemoteException;}
// 홈 인터페이스
public interface HelloHome extends EJBHome {Hello create() throws RemoteException, CreateException;}
// 빈 클래스
public class HelloBean implements SessionBean {public String sayHello(){
return "Hello EJB!";
}
public void ejbCreate() {}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void setSessionContext(SessionContext sc) {}}
// ejb-jar.xml

xmlns="http://java.sun.com/xml/ns/j2ee">

HelloEJB
hello.HelloHome
hello.Hello
hello.HelloBeanStateless



// 클라이언트
public class HelloClient {
public static void main(String[] args) throws Exception {Context ic = new InitialContext();Object objref =
ic.lookup("java:comp/env/ejb/HelloEJB");HelloHome home =
(HelloHome)PortableRemoteObject.narrow(objref,HelloHome.class);
Hello hello = home.create();
System.out.println(hello.sayHello());}
}


자바EE 5 애플리케이션 서버  


현재 여러 벤더들이 자바EE 5의 플랫폼을 내놓고 있다. 완성된 상용 수준의 제품은 아니지만 충분히 개발용으로 쓸 만하다. 국내 제품으로는 자 바EE 5 호환 인증을 세계 최초로 통과한 티맥스 소프트의 제우스 (JEUS) 6.0 프리뷰가 있다. 또한 썬마이크로시스템즈의 자바EE 5 SDK(또는 이의 오픈소스 구현인 글래스피시(GlassFish))도 나와 있다.
그 외에도 EJB 3.0만 지원하는 JBoss EJB 3.0과 Oracle Application
Server 10.1.3, BEA WebLogic Server EJB 3.0 프리뷰와 같은 제품도 있으니 참고하자.
여기에 제시된 예제들은 기본적으로 제우스 6.0을 기반으로 작성 되었으며 자바EE 5 SDK를 통해서도 구동할 수 있다.

● TmaxSoft JEUS 6.0 - http://www.tmax.co.kr
● Java EE 5 SDK - http://java.sun.com/javaee/
● GlassFish - https://glassfish.dev.java.net/
● JBoss - http://jboss.com/products/jbossas
● Oracle AS - http://www.oracle.com/technology/products/ias/index.html
● BEA WebLogic Server - http://www.bea.com/products/weblogic/server/




이미 EJB 프로그래밍을 해본 독자라면 알겠지만 매우 간단한 HelloEJB 예제에도 많은 코드가 필요하다. 또한 일반적인 자바 OOP 프로그래밍과 달리 EJB 만의 특수한 모델에 맞게 프로그래밍 해야 했다. 다음은 EJB 2.1의 대표적인 불편사항들을 정리 해 놓은 것이다.

● 컴포넌트 인터페이스 - 비즈니스 메소드를 정의한 부분으로 EJBObject/EJBLocalObject를 extends하여 정의한다. EJB만의 인터페이스 상속구조를 따라야 하므로 기존에 존재하던 일반 자바 인터페이스를 바로 재활용 할 수 없다. 또한 EJBObject를 extends한 원격 인터페이스의 경우 RMI(Remote Method Invocation) 인터페이스이므로 모든 메소드 정의에 RemoteException이 꼭 들어가야 한다.
● 홈 인터페이스 - Stateless 세션 빈의 경우 항상 똑 같이 위와 같은 create() 메소드를 정의해야 한다. EJB가 많아지면 반복적으로 코딩해야 한다.
● 빈 클래스 - 컴포넌트 인터페이스에 정의된 비즈니스 메소드를 구현한다. 하지만 해당 컴포넌트 인터페이스를 문법적으로 implements 하지 않아 실제 인터페이스와의 관계가 명시적으로 인식되지 않는다(IDE의 리팩토링 기능도 쓸 수 없다).
SessionBean 인터페이스를 항상 implements해야 하기 때문 에 쓰지도 않는 콜백 메소드를 모두 구현해야 한다.
● ejb-jar.xml - EJB에 대한 설정을 선언적으로 해주며 항상 있어야 한다. 컴포넌트 인터페이스, 홈 인터페이스, 빈 클래스의 관계가 여기서 나타나기 때문에 자바 코드와 별도로 항상 이 XML을 참고해야 EJB 구조를 알 수 있다. 설정이 많아지면 XML 내용과 구조가 복잡해진다.
● 클라이언트 - EJB를 얻기 위해 여러 단계를 거친다. JNDI lookup, 홈 객체 캐스팅, create 메소드 호출 등 비즈니스 로직과 관계 없는 코드가 많다. 또한 컴포넌트 인터페이스가 RemoteException을 던질 수 있기 때문에 항상 예외(checked Exception 처리를 해주어야 한다.

EJB 3.0에서는 위에서 제기한 상당수의 문제를 해결하였다.

<리스트 2>는 똑같은 예제를 EJB 3.0 방식으로 구현한 것이다.

실제 로직에 불필요한 코드의 양이 확 줄었고 POJO 방식으로 프로그래밍 모델이 단순해진 것을 확인할 수 있다. 새롭게 도입 된 비즈니스 인터페이스는 이제 보통의 자바 인터페이스이고 빈 클래스가 이를 구현(implements)하는 POJO 객체이다. EJB에 관련된 내용은 어노테이션을 통해 표기되므로 EJB 구조를 코드 상에서 파악할 수 있다. 따라서 XML 설정(ejb-jar.xml)은 더 이 상 사용할 필요가 없다. 반복되던 홈 인터페이스 정의도 없어졌 다. 클라이언트 코드도 매우 깔끔해졌는데 인젝션(Injection)을 통해 EJB를 얻는 과정이 단순화 되었다.


 <리스트 2> EJB 3.0으로 구현한 HelloEJB와 클라이언트

// 비즈니스 인터페이스
@Remote
public interface Hello {
public String sayHello();
}
// 홈 인터페이스 - 없음
// 빈 클래스
@Stateless
public class HelloBean implements Hello {public String sayHello(){
return "Hello EJB!";
}
}
// ejb-jar.xml - 불필요함
// 클라이언트
public class HelloClient {
@EJB
private static Hello hello;
public static void main(String[] args) {System.out.println(hello.sayHello());}
}


앞으로는 LottoApp 이라고 하는 간단한 샘플 애플리케이션을 통해 EJB 3.0 코드를 살펴볼 것이다. LottoApp는 로또를 발급 하는 애플리케이션으로 웹 인터페이스를 통해 EJB를 호출한다.
웹을 통해 들어온 사용자 요청은 서블릿(Servlet)이 다음의 EJB 들을 사용하여 처리하게 된다.

● LotteryBean - 로또 번호를 발급하는 Stateful 세션 빈● RandomGeneratorBean - 1에서 45사이의 랜덤 숫자를 발생시키는 Stateless 세션 빈
● TimestampBean - 현재 날짜를 돌려주는 Stateless 세션 빈

세션 빈(Session Bean)


먼저 세션 빈의 변화된 특징에 대해서 먼저 살펴보자. 새롭게 도입된 세션 빈의 비즈니스 인터페이스(business interface)는 EJBObject/EJBLocalObject를 extends한 인터페이스대신 일반적인 자바 인터페이스를 사용한다. 단지 원격 인터페이스인지
로컬 인터페이스인지 구분하기 위해 @Remote 또는 @Local 어노테이션을 아래와 같이 달아주면 된다.

@Remote // 원격 비즈니스 인터페이스public interface RandomGenerator {public int getNumber( );
}

@Local // 로컬 비즈니스 인터페이스public interface Timestamp {
public String today( );
}

위의 RandomGenerator 인터페이스에서 보듯이 원격 인터페이스의 경우에 java.rmi.Remote 인터페이스를 extends 하지 않아도 되어(선택사항임) 모든 메소드에 RemoteException을 붙일 필요가 없어졌다. 따라서 클라이언트도 이제 이 체크 익셉션 (checked Exception)을 항상 처리할 필요가 없다. 네트워크와 같은 시스템 문제는 이제 RuntimeException인 EJBException으로 발생하기 때문에 익셉션 처리에 좀 더 유연성을 가지게 됐다.
물론 여전히 EJB에 대한 원격 접근은 RMI/IIOP를 통해서 이루어진다. 이제 비즈니스 메소드를 구현하는 빈 클래스를 살펴보자.

@Stateless
public class RandomGeneratorBean implementsRandomGenerator {
public int getNumber() {
Random random = new Random();
return 1 + random.nextInt(45);}
}

빈 클래스는 비즈니스 인터페이스를 implements 한 POJO 객체로 정의한다. 세션 빈임을 표시하기 위해 @Stateless나 @Stateful 어노테이션만 달아주면 된다. 또한 빈 클래스가 SessionBean 인터페이스를 implements 할 필요가 없어 사용하지 않는 콜백 메소드를 모두 구현할 필요가 없어졌다. 필요한 콜 백의 경우 콜백 어노테이션을 통해 콜백 메소드를 지정하면 된 다. <리스트 3>에 있는 좀더 복잡한 LotteryBean Stateful 세션 빈을 살펴보자.


 <리스트 3> LotteryBean Stateful 세션 빈

// e.g
@Stateful
@Interceptors(ProfilingInterceptor.class) // 인터셉터 지정public class LotteryBean implements Lottery, Serializable{
@Resource // 인젝션
private SessionContext sc;
@PostConstruct // PostConstruct 콜백 지정private void postConstruct(){
//...
}
//...
@Remove // Remove 메소드 지정
public void remove(){
// bye~
}
}


<리스트 3>에서는 @PostConstruct 어노테이션을 통해서 PostConstruct 콜백 메소드를 지정하고 있다. XXX void XXX() 형태의 시그너처를 가진 모든 메소드는 어노테이션으로 콜백 메소드로 지정할 수 있기 때문에 하나의 메소드로 여러 콜백을 처 리 하는 등 상당한 유연성을 제공한다. 다음은 콜백 어노테이션 의 종류별 설명이다.

● @PostConstruct - 빈 인스턴스가 생성된 후에 불리며 예전 의 ejbCreate( )와 동일
● @PreDestroy - 빈 인스턴스가 삭제되기 전에 불리며 예전 의 ejbRemove( )와 동일
● @PrePassivate - Stateful 세션 빈 인스턴스가 passivation 되기 전에 불리며 예전의 ejbPassivate( )와 동일● @PostActivate - Stateful 세션 빈 인스턴스가 activation 된 후에 불리며 예전의 ejbActivate( )와 동일
LotteryBean의 경우 또한 SessionContext 객체를 @Re source 어노테이션을 사용하여 인젝션해서 가져오고 있다. 인젝션은 정확한 용어로 디펜던시 인젝션(Dependency Injection) 혹 은 리소스 인젝션(Resource Injection)이라고 하는데 해당 컴포넌트가 의존하는 외부 리소스를 가져오는 새로운 방법이다. 기존에 개발자가 직접 JNDI lookup하던 것을 이제 컨테이너가 알아서 필드 값에 넣어주게 된 것이다. 인젝션은 필드에 적용할 수도 있고 아래와 같이 setter 메소드에도 적용할 수 있다.

@Resource
private void setSessionContext(SessionContext sc){this.sc = sc;
}

또, 인젝션을 이용하여 DataSource나 EJB 참조, Connection Factory, Queue, Topic, UserTransaction 등의 외부 리소스를 가져올 수 있다.
LotteryBean은 Stateful 세션 빈이기 때문에 빈을 제거하기 위 한 Remove 메소드를 @Remove 어노테이션으로 지정한다.
Remove 메소드는 임의의 비즈니스 메소드에 지정할 수 있으며 메소드의 수행이 끝나면 해당 Stateful 세션 인스턴스가 제거된다.

인터셉터 (Interceptor)
<리스트 3> LotteryBean의 경우 @Interceptors 어노테이션을 이용하여 EJB 3.0에서 새로 추가된 인터셉터(interceptor)클래스를 지정하고 있다.

인터셉터는 현재 많은 관심을 끌고 있는 AOP(AspectOriented Programming)의 개념에서 파생된 것으로 비즈니스메소드가 불리기 전에(혹은 후에) 해야 하는 공통적인 부분(cross-cutting concerns)을 별도로 구현할 수 있게 해준다. 인터셉터는 다양한 목적으로 활용될 수 있는데, 예를 들어 메소드의 수행시간을 측정하거나 예외처리, 파라미터 검사, 사용자 고유의 보안 검사 등에 이용될 수 있다. <리스트 4>는 메소드 수행 시간을 측정하는 인터셉터이다.


 <리스트 4> ProfilingInterceptor 인터셉터

public class ProfilingInterceptor {@AroundInvoke
public Object intercept(InvocationContext invocation)throws Exception {
long start = System.currentTimeMillis();try {
return invocation.proceed(); // 다음 인터셉터 메소드나 실제 빈 메소드를 수행
} finally {
long time = System.currentTimeMillis() - start;Method method = invocation.getMethod();System.out.println("Profiling: " +method.toString() + " took " + time + "(ms)");}
}
//...
}


인터셉터 클래스에는 하나의 인터셉트 메소드가 @AroundInvoke라는 어노테이션을 통해 지정된다. 인터셉트 메소드는 빈클래스 내에서 구현하거나 ProfilingInterceptor와 같이 별도로 인터셉트 클래스로 만들 수 있으며 인터셉트 클래스는 재활용 할 수 있다는 장점도 있다. 중요한 것은 인터셉터는 여러 개가 있으면 체인으로 구성되기 때문에 Invocation Context.proceed()를 호출해야 다음 인터셉터나 실제 빈 메소드로 넘어가게 된다는 점이다.

인터셉터는 또한 디폴트 레벨로 정의하여 모든 빈에게 적용시킬 수도 있고 메소드별로 정의할 수도 있으며 PostConstruct,PreDestroy와 같은 콜백 인터셉트 메소드도 가질 수 있어 콜백을 별도의 핸들러 클래스로 처리할 수 있다.

클라이언트 뷰(Client view)
기존의 EJB를 호출하는 클라이언트 코드는 실제 비즈니스 메소드를 수행하기 전에 JNDI lookup 코드 등 복잡한 과정을 수행해야 했다. EJB 3.0에서는 인젝션과 단순화된 lookup을 통해 EJB를 얻어오는 과정이 단순해졌다. <리스트 5> 서블릿 클라이언트를 살펴보자.


 <리스트 5> 서블릿 클라이언트

@EJB(name="ejb/lottery", beanName="LotteryBean",beanInterface=Lottery.class)
public class PlayLotteryServlet extends HttpServlet {@EJB // 인젝션
private RandomGenerator generator;protected void processRequest(HttpServletRequest request,HttpServletResponse response) throwsServletException, IOException {//...
InitialContext initContext = new InitialContext();Lottery lottery = (Lottery)
initContext.lookup("java:comp/env/ejb/lottery");


클래스에 붙어 있는 @EJB(name="ejb/lottery"...) 어노테이션은 기존의 와 같이 참조되는 EJB에 대해 정의를 한 것으로 EJB를 JNDI lookup으로 가져오기 위해 필요하다. 이렇게 정의를 하면 해당 EJB 레퍼런스는 ENC(EnvironmentNaming Context)에 해당하는 java:comp/env 네임스페이스에 들어간다. 맨 아랫줄의 lookup 코드가 해당 EJB 레퍼런스를 얻어오는 것을 보여주고 있는데 이처럼 홈 객체를 통하지 않고 바로 비즈니스 객체를 받아 올 수 있다. 비즈니스 인터페이스는 컨테이너가 lookup 할 때 기존의 홈 객체의 create 메소드와 같은과정을 대신해 준다. 이 경우에는 새로운 Stateful 세션 빈 인스턴스를 생성하고 이에 대한 레퍼런스를 얻어오게 된다.

인젝션을 이용하면 JNDI lookup 보다 더 쉽게 EJB 레퍼런스를 얻어올 수 있다. 필드에 붙어있는 @EJB 어노테이션은 필드
의 타입을 참조하여 해당 EJB 레퍼런스를 필드에 넣어준다.

웹서비스 엔드포인트
EJB 3.0에서는 세션 빈을 웹서비스로 노출하는 것도 매우 간단해졌다. 이에 대한 것은 특집 3부 JAX-WS 2.0을 참조하자.

MDB(Message-Driven Bean)


메시지를 처리하기 위한 MDB도 EJB 3.0에서 POJO 형태로단순화 되었다. <리스트 6>에서 보듯이 MDB도 어노테이션을 통해 필요한 설정을 하며 이제 MessageListener를 직접 implements 하게 된다.


 <리스트 6> MDB 예제

@MessageDriven( activationConfig = {@ActivationConfigProperty(propertyName="acknowlegeMode",
 propertyValue="Auto-acknowledge"),
@ActivationConfigProperty(propertyName="destinationType",
 propertyValue="javax.jms.Queue")})
public class SimpleMessageBean implements MessageListener{
@Resource
private MessageDrivenContext mdc;public void onMessage(Message inMessage) {//...
}
}


디스크립터
EJB 3.0에서는 ejb-jar.xml 표준 디스크립터의 대부분을 어노테이션으로 설정할 수 있게 되었다. 물론, 어노테이션이 모든 것을 커버하지 않기 때문에 어떤 경우에는 디스크립터에 설정이 필요할 때가 있기는 하다. 또한 보안 설정(security role)과 같이 실제 런타임 환경에서 바뀔 수 있는 부분은 디스크립터에 설정을 하는 것이 좋다. EJB 3.0에서는 디스크립터 오버라이딩을 지원하기 때문에 필요한 정보만 디스크립터에서 설정할 수 있다. 만약 어노테이션 대신에 예전처럼 디스크립터에 모든 정보를 주고 싶으면 그렇게 할 수도 있다.

  자바 퍼시스턴스(Java Persistence) API

이제부터는 데이터를 관계형 데이터베이스(RDBMS)에 저장하기 위한 퍼시스턴스 레이어 기술인 자바 퍼시스턴스에 대해서 살펴보자.

애플리케이션에서 다루는 객체 모델을 데이터베이스의 데이터모델로 저장하고 매핑하기 위해 다양한 ORM(Object-Relational Mapping) 기술들이 정의되어 왔다. EJB에서는 엔티티 빈이 그 기술을 제공해왔다. 엔티티 빈은 개발하기가 까다롭고 EJB 자체의 복잡한 프로그래밍 모델을 사용해야 할 뿐 아니라 객체간의 상속(Inheritance) 관계를 매핑하지 못하는 등의 제약이 있어서 실제로 쓰기에는 불편하였다. 따라서 최근에는 하이버네이트와 같은 POJO 기반의 다른 ORM 기술들이 점점 더 인기를 끌어 왔다. 결국 EJB 3.0 표준화 그룹인 JSR 220에서 POJO를 표준화된 ORM 기술로 채택하게 되었는데 그것이 바로자바 퍼시스턴스 API 스펙이다. 실제로 자바 퍼시스턴스 스펙 제정에는 Gavin King(하이버 네이트 제작자)을 포함하여 여러 ORM 기술 개발자들이 참여했기에 때문에 유사한 점이 많다. 먼저 자바 퍼시스턴스가 가지는 특징이 어떤 것이 있는지 살펴보자.

● POJO 기반의 단순한 퍼시스턴스 모델
● 표준화된 O/R 매핑 - 어노테이션 혹은 XML 사용, 디폴트 규칙 적용으로 대부분의 경우 별도의 O/R 매핑을 지정할 필요가 없음
● 객체간의 상속관계 지원
● EJBQL에 비해서 발전된 쿼리 언어 - Update/delete, 서브쿼리, 네이티브 쿼리 지원
● 자바EE 환경뿐만 아니라 자바SE 환경 지원
● 프로바이더(Provider)을 플러그인 해서 사용 가능POJO 기반의 단순화된 프로그래밍 모델을 지원하기 때문에 엔티티 빈처럼 복잡한 EJB 객체 모델대신 일반적인 자바 클래스 하나로 쉽게 엔티티를 만들 수 있다. 필요한 O/R 매핑은 POJO 위에 어노테이션으로 표기하거나 XML로 표기할 수 있고 상속 관계를 지원하므로 기존에 존재하던 객체 모델을 쉽게 자바 퍼시스턴스 엔티티로 전환해서 사용할 수 있다. 또한 엔티티 객체는 POJO 이므로 DTO(Data Transfer Object)로 바로 사용이 가능하다.

앞으로는 OrderApp이라고 하는 간단한 애플리케이션을 통해 자바 퍼시스턴스의 전반적인 내용을 살펴볼 것이다. OrderApp는 EJB로 구현된 단순한 주문시스템인데 EJB에서 자바 퍼시스턴스 API를 사용하여 데이터를 다루게 된다. 여기에서 사용된 데이터 모델은 <그림 1>과 같다.

<그림 1> 예제 데이터 모델


엔티티(Entity)


DB의 데이터 모델은 자바 퍼시스턴스에서 엔티티(Entity)로 표현된다. 엔티티는 데이터 모델을 객체 모델로 표현한 것으로 EJB 2.1의 엔티티 빈과 유사하지만 자바 퍼시스턴스에서는 별도의 인터페이스 없이 POJO 클래스로 표현된다. 클래스에는 엔티티임을 표시하기 위해 @Entity 어노테이션이 달려있다. 실제로 <그림 1>의 데이터 모델에 대해서 엔티티가 어떻게 만들어 지는지 <리스트 7>을 보도록 하자.


 <리스트 7> 엔티티 클래스

// Customer.java
@Entity
public class Customer implements Serializable {@Id
private String id;
private String name;
private String address;
@OneToMany(mappedBy="customer",cascade={CascadeType.ALL})
private List orders;
//...
}
// Item.java
@Entity
public class Item implements Serializable {//...
@Id @GeneratedValue
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;
}
//...
}
// Orders.java
@Entity
public class Orders implements Serializable {@Id @GeneratedValue
private Long id;
@ManyToOne
private Customer customer;
@ManyToMany
@JoinTable(name="ORDERAPP_ORDERS2ITEMS")private Collection items;//...
}


엔티티는 기본적으로 하나의 엔티티 클래스에 대해 하나의DB 테이블이 매핑된다(이 경우에는 M:N(many to many) 관계를 위한 테이블이 하나 더 만들어진다). 필드나 자바빈즈(JavaBeans) 프로퍼티 값은 DB의 컬럼에 매핑된다. 어떤 부분에 어노테이션이 달려있는지에 따라서 필드 접근 타입(Fieldaccess type)과 프로퍼티 접근 타입(Property access type)이 결정된다. 예제에서는 Customer, Orders 엔티티의 경우 필드 들이 매핑되고, Item 엔티티의 경우 프로퍼티 들이 DB 컬럼에 매핑된다.

DB와의 매핑은 O/R 매핑 어노테이션 혹은 XML(orm.xml디스크립터)로 이루어진다. Primary Key를 나타내는 필드나 관계(Relationship)를 나타내는 필드를 제외하고는 대부분 디폴트정책에 따라서 매핑되기 때문에 별도의 어노테이션을 지정하지 않아도 된다. 다음은 위의 엔티티에서 사용된 어노테이션들에 대한 설명이다. 이외에도 O/R 매핑에 사용할 수 있는 많은 표준 어노테이션이 있다.

● @Id - Primary Key에 해당하는 ID 필드임을 표시한다.
● @GeneratedValue - DB에 의해서 값이 자동으로 생성되는 필드임을 표시한다.
● @OneToMany - 1:N 관계를 위한 표현하기 위한 필드임을 표시한다.
● @ManyToOne - N:1 관계를 위한 표현하기 위한 필드임을 표시한다.
● @ManytoMany - M:N 관계를 위한 표현하기 위한 필드임을 표시한다.
● @JoinTable - 관계를 표현할 때 어떤 테이블을 사용할 것인지에 대해 표시한다.

엔티티는 일반 클래스와 비슷하지만 항상 디폴트 생성자(Default constructor)를 만들어 주어야 한다는 특징이 있다. 디폴트 생성자는 엔티티 인스턴스를 내부적으로도 생성해서 사용하기 때문에 필요하다. 또한 예제의 경우 엔티티들이 Serializable 객체로 구현되었는데 이는 필수사항은 아니지만 엔티티를 DTO(Data Transfer Object)로 사용하여 원격으로 보낼 때 필요하다.

EntityManager API


DB에서 엔티티를 가져오거나, 생성, 삭제 하는 일은 모두 EntityManager API를 통해서 이루어진다. EntityManager는 누가 관리하느냐에 따라 크게 두 가지로 구분된다.

● Container-managed EntityManager - 자바EE 환경에서만 사용되며 컨터이너가 EntityManager 인스턴스의 Lifecycle을 관리한다. JTA 트랜잭션에 자동으로 연결되기 때문에 자바EE 환경에서 유용하다.

● Application-managed EntityManager - 자바EE, 자바SE환경에서 모두 사용되며 개발자가 EntityManager 인스턴스의 Lifecycle을 직접 관리하며 트랜잭션도 직접 관리해야 한다.

EntityManager 객체는 Container-managed EntityManager의 경우 인젝션이나 lookup을 통해서 바로 얻어올 수 있다.

// 인젝션 이용. 해당 필드나 메소드에 @PersistenceContext 표기@PersistenceContext
private EntityManager em;

// lookup 이용. 클래스에 @PersistenceContext를 표기해 EntityManager를 ENC 네임스페이스에 등록
@PersistenceContext(name="em", unitName="HR")public class HelloBean implements Hello {//...
InitialContext ic = new InitialContext();EntityManager em = (EntityManager)ic.lookup("java:comp/env/em");

Application-managed EntityManager의 경우 EntityManagerFactory API를 통해서 EntityManager 객체를 얻게 된다. 자바EE 환경에서는 인젝션이나 lookup을 통해 EntityManagerFactory를 얻어올 수 있다.

@PersistenceUnit
private EntityManagerFactory emf;//...
EntityManager em = emf.createEntityManager();
엔티티 객체를 만드는 것은 일반 자바 객체를 생성하듯이 new생성자를 사용하면 되며 이를 DB에 저장하려면 다음과 같이EntityManager.persist() 메소드를 호출해야 한다.

Customer customer = new Customer(id, name, address);em.persist(customer);

DB에서 하나의 엔티티 객체를 가져오려면 EntityManager.find() 메소드를 호출한다. 이 경우에는 Primary key에 해당하는 값을 통해서 엔티티를 가져올 수 있다.

Customer c = em.find(Customer.class, id);
또는 자바 퍼시스턴스 쿼리 랭귀지(Java Persistence QueryLanguage)를 이용하여 모든 엔티티 들을 가져오거나 특정 필드값을 비교하여 엔티티를 가져올 수 있다.

Query query = em.createQuery("select c from Customer c");List list = query.getResultList();
엔티티의 필드를 수정하려면 엔티티 객체의 필드를 수정하면 된다. 해당 필드는 EntityManager.flush() 를 호출하거나 트랜잭션이 commit 될 때 DB와 동기화 된다.

Customer c = em.find(Customer.class, id);
c.setAddress(address);
엔티티를 삭제할 때에는 EntityManager.remove()를 호출한다.

em.remove(customer);

엔티티 간의 관계(Relationship)는 다른 엔티티에 대한 레퍼런스로 표현되는데 관계를 수정하려면 레퍼런스 값을 바꾸면 된다. 혹은 ManyToMany, OneToMany와 같이 여러 개의 엔티티와 관계를 맺고 있을 때는 Collection 객체로 표현되므로 해당micro software 2006+7 129
Collection 내용을 바꾸면 된다.

public class Customer //...
@OneToMany(mappedBy="customer", cascade={CascadeType.
PERSIST, CascadeType.REMOVE})
private List orders;
public void addOrder(Orders o){orders.add(o);
}

관계의 경우에는 해당 엔티티에 대해 오퍼레이션(persist,remove 등) 할 때 관계된 엔티티도 같이 persist, remove 될 것인지를 지정하는 cascade 타입을 지정할 수 있다. 기본적으로는 cascade 값이 지정되지 않지만 위와 같이 지정하면 Customer엔티티가 생성, 삭제될 때 관계된 Orders 엔티티도 같이 생성, 삭제된다.

쿼리(Query)


자바 퍼시스턴스에서는 자바 퍼시스턴스 쿼리 랭귀지(JavaPersistence Query Language)라고 하는 객체 지향적인 쿼리 언어를 제공한다. EJBQL와 유사하지만 자바 퍼시스턴스 쿼리는 Bulk Update, Delete 문 뿐만 아니라 서브쿼리(Subquery)도 지원하므로 그 활용범위가 넓다. 또한 DB에 특화된 SQL 문을 직접 사용할 수 있는 네이티브 쿼리(Native Query)도 지원한다.
쿼리를 실제로 사용할 때는 동적으로 바로 생성해서 사용하거나 네임드 쿼리(Named query)라고 하는 정적인 방식으로 사용할 수 있다.

// 동적 쿼리
Query query = em.createQuery("select c from Customer c wherename=:name");
query.setParameter("name", name);List list = query.getResultList();
// 정적 쿼리 - @NamedQuery는 엔티티에 명기@NamedQuery(name="findCustomerByName", query="select cfrom Customer c where c.name=:name")
Query query = em.createNamedQuery("findCustomerByName");query.setParameter("name", name);List list = query.getResultList();
네임드 쿼리(Named Query)는 엔티티 클래스에 직접 명기되며 재활용되기 때문에 해당 엔티티에 대해서 자주 활용되는 쿼리의 경우 미리 정의해 놓고 사용하면 편리하다.

엔티티의 라이프사이클과 퍼시스턴스 컨텍스트


EntityManager의 동작은 퍼시스턴스 컨텍스트(PersistenceContext)와 이와 관련된 엔티티 인스턴스의 라이프사이클을 알아야 이해할 수 있다. 이 부분은 자바 퍼시스턴스에서 중요하고도 조금 복잡한 부분이다. 퍼시스턴스 컨텍스트(PersistenceContext)는 현재 관리되고 있는 엔티티 인스턴스들의 집합을 말하는데 엔티티 인스턴스의 상태는 EntityManager 오퍼레이션과 밀접한 관련이 있다. <그림 2>는 엔티티의 상태가 어떻게 변화하는지 보여준다.

<그림 2> EntityManager 오퍼레이션에 따른 엔티티의 라이프사이클


● New - 엔티티 인스턴스가 생성되었을 때의 상태로 DB와 연결되어 있지 않다. persist( )를 통해서 DB에 저장되며managed 상태가 된다.

● Managed - 엔티티 인스턴스가 퍼시스턴스 컨텍스트에 의해서 관리되는 상태로 엔티티 인스턴스 값의 변화가 DB에 반영된다. Container-managed EntityManager의 경우 트랜잭션이 종료되면 모든 엔티티는 detached 상태가 된다.

● Detached - 엔티티 인스턴스가 퍼시스턴스 컨텍스트에 의해서 더 이상 관리되지 않는 상태로 DB와 연결되지 않는다.
EntityManager.merge( )를 통해서 다시 managed 상태로 만들 수 있다.

● Removed - DB에서 해당 엔티티가 삭제될 상태이다.

패키징(Packaging)


이제 엔티티 클래스들을 실제로 애플리케이션에 패키징 시키는 방법에 대해 알아보자. 엔티티 클래스를 패키징 할 때는META-INF/persistence.xml 디스크립터가 꼭 필요하다.

persistence.xml은 관련된 엔티티 클래스들과 이에 대한 DB

DataSource 설정 등과 같이 퍼시스턴스 유닛(PersistenceUnit_에 대한 정보를 담고 있다. 퍼시스턴스 유닛은 엔티티를 묶는 단위이며 이것은 하나의 DB에만 매핑된다. 퍼시스턴스 유닛은 유닛 이름(Unit Name) 식별자를 가지는데 이 식별자는 EntityManager나 EntityManagerFactory를 얻을 때 사용된다.

// persistence.xml
<persistence version="1.0"xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="orderunit"transaction-type="JTA">
<jta-data-source>jdbc/sample</jta-data-source><properties> <!-- 프로바이더 별 설정 --><property name="toplink.ddl-generation"value="createtables"/></properties>
</persistence-unit>
</persistence>

패키징은 다음과 같이 JAR 파일이나 디렉토리에 할 수 있으며 이 단위를 퍼시스턴스 유닛 루트(Persistence Unit Root)라고 한다.

● EAR library 디렉터리의 JAR 파일(e.g. orderapp.ear!/lib/entities.jar)● EAR 루트 디렉터리의 JAR 파일(e.g. orderapp.ear!/entities.jar)● EJB JAR 파일 (e.g. orderapp.ear!/orderapp-ejb.jar)● WAR 파일의 WEB-INF/classes
(e.g. orderapp-web.war!/WEB-INF/classes)● WAR 파일의 WEB-INF/lib에 JAR 파일(e.g. orderapp-web.war!/WEB-INF/lib/entities.jar)● application client의 JAR 파일(e.g. orderapp.ear!/orderapp-client.jar)

자바SE 환경에서 사용하기


자바SE 환경에서 사용할 때는 Application-managedEntityManager만 사용할 수 있으며 JDBC 드라이버에 국한된 로컬(resource-local) 트랜잭션만 쓸 수 있다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("orderunit");
EntityManager em = emf.createEntityManager();//...
EntityTransaction tx = em.getTransaction();tx.begin();
em.persist(customer);
tx.commit();
//...
em.close();
emf.close();

자바SE에서는 javax.persistence.spi.Persistence 부트스트랩API를 사용해서 EntityManagerFactory를 얻는다는 것과 EntityTransaction API를 이용하여 직접 트랜잭션 컨트롤을 해주어야 한다는 차이가 있다. 또한 persistence.xml 파일의 위치는 클래스패스 상에서 META-INF/persistence.xml에 있어야 하며, 자바EE 환경과 달리 다음과 같이 모든 엔티티 클래스를 명시적으로 나열해 주어야 한다.

<persistence-unit name="orderunit">
<class>orderapp.entities.Customer</class>
<class>orderapp.entities.Item</class>
<class>orderapp.entities.Orders</class>

지금까지 EJB 3.0과 자바 퍼시스턴스 API 1.0 내용과 예제코드를 살펴보았다. 많은 내용을 깊이 있게 다루지는 못하였지만 전반적인 특징을 살펴보는데 도움이 되었으리라 믿는다.

2006년 자바원(JavaOne)에서도 EJB 3.0과 자바 퍼시스턴스에 관련된 세션이 많았다. 필자의 블로그에 가면 세션에 대한 소개와 프리젠테이션 PDF를 받을 수 있다(http: //weblogs.java.net/blog/guruwons/archive/2006/06/ejb_30_sessions.html). @

참고 자료:1. Java EE 5 Tutorial - http://java.sun.com/javaee/5/docs/tutorial/doc/2. SDN Article: The Java Persistence API - A Simpler Programming Model forEntity Persistence - http://java.sun.com/developer/technicalArticles/J2EE/jpa/3. JSR 220 - http://jcp.org/en/jsr/detail?id=220이달의 디스켓 : EJB30samp.zip


* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.