2 Cover Story POJO 로돌아온 EJB 3.0 과자바퍼시스턴스 API 이번특집에서는자바EE 5의변화중가장눈에띄는 EJB 3.0과자바퍼시스턴스 (Java Persistence) API 기술을소개하고자한다. 이기술들은기존 EJB 2.1에비해서많은부분이달라졌다. 자바SE 5의메타데이터어노테이션 (metadata annotation) 을도입하고 POJO(Plain Old Java Object) 기반의단순화된프로그래밍모델을제공한다는것이가장큰특징이다. 더불어기존의 EJB 엔티티빈은자바퍼시스턴스 API로대체되면서좀더쉽고강력한 ORM 기술을제공한다. 김원석 guruwons@tmax.co.kr 티맥스소프트에서 WAS(Web Application Server) 제품인제우스 (JEUS) 를개발하고있다. 주로 EJB 컨테이너와백엔드부분에대한일을담당하고있다. 또한자바표준화단체인 JCP에서자바EE 5의스펙을제정하는전문가그룹멤버로활동을해왔다. 최근에는오픈소스자바EE 플랫폼인글래스피시 (GlassFish) 프로젝트에도참여하고있다. 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 설정에서탈 micro software 2006+7
피할수있게되고코드상에서연관된설정을할수있기때문에코드의가독성도높아지게되었다. 물론그외에도여러방면에서개발자편의성을높이기위한노력이이루어졌다. 자바퍼시스턴스 (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 방식으로구현한것이다. 자바 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/ < 리스트 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 <ejb-jar version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee"> <enterprise-beans> <session> <ejb-name>helloejb</ejb-name> <home>hello.hellohome</home> <remote>hello.hello</remote> <ejb-class>hello.hellobean</ejb-class> <session-type>stateless</session-type> </session> </enterprise-beans> </ejb-jar> // 클라이언트 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()); micro software 2006+7
cover story 2 _ EJB 3.0 과자바퍼시스턴스 API 이미 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한인터페이스대신일반적인자바인터페이스를사용한다. 단지원격인터페이스인지 micro software 2006+7
로컬인터페이스인지구분하기위해 @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 implements RandomGenerator { public int getnumber() { Random random = new Random(); return 1 + random.nextint(45); 빈클래스는비즈니스인터페이스를 implements 한 POJO 객체로정의한다. 세션빈임을표시하기위해 @Stateless나 @Stateful 어노테이션만달아주면된다. 또한빈클래스가 SessionBean 인터페이스를 implements 할필요가없어사용하지않는콜백메소드를모두구현할필요가없어졌다. 필요한콜백의경우콜백어노테이션을통해콜백메소드를지정하면된다. < 리스트 3> 에있는좀더복잡한 LotteryBean Stateful 세션빈을살펴보자. < 리스트 3> 에서는 @PostConstruct 어노테이션을통해서 < 리스트 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~ 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){ micro software 2006+7
cover story 2 _ EJB 3.0 과자바퍼시스턴스 API 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(Aspect Oriented 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)"); 인터셉터클래스에는하나의인터셉트메소드가 @Around Invoke라는어노테이션을통해지정된다. 인터셉트메소드는빈클래스내에서구현하거나 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) throws ServletException, IOException { InitialContext initcontext = new InitialContext(); Lottery lottery = (Lottery) initcontext.lookup("java:comp/env/ejb/lottery"); 클래스에붙어있는 @EJB(name= ejb/lottery...) 어노테이션은기존의 <ejb-ref> 와같이참조되는 EJB에대해정의를한것으로 EJB를 JNDI lookup으로가져오기위해필요하다. 이렇게정의를하면해당 EJB 레퍼런스는 ENC(Environment Naming Context) 에해당하는 java:comp/env 네임스페이스에들어간다. 맨아랫줄의 lookup 코드가해당 EJB 레퍼런스를얻어오는것을보여주고있는데이처럼홈객체를통하지않고바로비즈니스객체를받아올수있다. 비즈니스인터페이스는컨테이너가 lookup 할때기존의홈객체의 create 메소드와같은과정을대신해준다. 이경우에는새로운 Stateful 세션빈인스턴스를생성하고이에대한레퍼런스를얻어오게된다. 인젝션을이용하면 JNDI lookup 보다더쉽게 EJB 레퍼런스를얻어올수있다. 필드에붙어있는 @EJB 어노테이션은필드 micro software 2006+7
의타입을참조하여해당 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) { 애플리케이션에서다루는객체모델을데이터베이스의데이터모델로저장하고매핑하기위해다양한 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) 을플러그인해서사용가능 디스크립터 EJB 3.0에서는 ejb-jar.xml 표준디스크립터의대부분을어노테이션으로설정할수있게되었다. 물론, 어노테이션이모든것을커버하지않기때문에어떤경우에는디스크립터에설정이필요할때가있기는하다. 또한보안설정 (security role) 과같이실제런타임환경에서바뀔수있는부분은디스크립터에설정을하는것이좋다. EJB 3.0에서는디스크립터오버라이딩을지원하기때문에필요한정보만디스크립터에서설정할수있다. 만약어노테이션대신에예전처럼디스크립터에모든정보를주고싶으면그렇게할수도있다. POJO 기반의단순화된프로그래밍모델을지원하기때문에엔티티빈처럼복잡한 EJB 객체모델대신일반적인자바클래스하나로쉽게엔티티를만들수있다. 필요한 O/R 매핑은 POJO 위에어노테이션으로표기하거나 XML로표기할수있고상속관계를지원하므로기존에존재하던객체모델을쉽게자바퍼시스턴스엔티티로전환해서사용할수있다. 또한엔티티객 Customer Item ID string PRIMARY KEY ID int PRIMARY KEY NAME string NAME string ADDRESS string 자바퍼시스턴스 (Java Persistence) API 1 N 이제부터는데이터를관계형데이터베이스 (RDBMS) 에저장하기위한퍼시스턴스레이어기술인자바퍼시스턴스에대해서 N Order ID int PRIMARY KEY M 살펴보자. < 그림 1> 예제데이터모델 micro software 2006+7
cover story 2 _ EJB 3.0 과자바퍼시스턴스 API 체는 POJO 이므로 DTO(Data Transfer Object) 로바로사용이가능하다. 앞으로는 OrderApp이라고하는간단한애플리케이션을통해자바퍼시스턴스의전반적인내용을살펴볼것이다. OrderApp 는 EJB로구현된단순한주문시스템인데 EJB에서자바퍼시스턴스 API를사용하여데이터를다루게된다. 여기에서사용된데이터모델은 < 그림 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> 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<Item> items; 엔티티는기본적으로하나의엔티티클래스에대해하나의 DB 테이블이매핑된다 ( 이경우에는 M:N(many to many) 관계를위한테이블이하나더만들어진다 ). 필드나자바빈즈 (JavaBeans) 프로퍼티값은 DB의컬럼에매핑된다. 어떤부분에어노테이션이달려있는지에따라서필드접근타입 (Field access 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 - 관계를표현할때어떤테이블을사용할것인지에대해표시한다. micro software 2006+7
엔티티는일반클래스와비슷하지만항상디폴트생성자 (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 Entity Manager의경우인젝션이나 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 ); @PersistenceUnit private EntityManagerFactory emf; EntityManager em = emf.createentitymanager(); 엔티티객체를만드는것은일반자바객체를생성하듯이 new 생성자를사용하면되며이를 DB에저장하려면다음과같이 EntityManager.persist() 메소드를호출해야한다. Customer customer = new Customer(id, name, address); em.persist(customer); DB에서하나의엔티티객체를가져오려면 Entity Manager.find() 메소드를호출한다. 이경우에는 Primary key 에해당하는값을통해서엔티티를가져올수있다. Customer c = em.find(customer.class, id); 또는자바퍼시스턴스쿼리랭귀지 (Java Persistence Query Language) 를이용하여모든엔티티들을가져오거나특정필드값을비교하여엔티티를가져올수있다. 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); Application-managed EntityManager의경우 Entity ManagerFactory API를통해서 EntityManager 객체를얻게된다. 자바EE 환경에서는인젝션이나 lookup을통해 EntityManagerFactory를얻어올수있다. 엔티티간의관계 (Relationship) 는다른엔티티에대한레퍼런스로표현되는데관계를수정하려면레퍼런스값을바꾸면된다. 혹은 ManyToMany, OneToMany와같이여러개의엔티티와관계를맺고있을때는 Collection 객체로표현되므로해당 micro software 2006+7
cover story 2 _ EJB 3.0 과자바퍼시스턴스 API Collection 내용을바꾸면된다. public class Customer @OneToMany(mappedBy= customer, cascade={cascadetype. PERSIST, CascadeType.REMOVE) private List<Orders> orders; public void addorder(orders o){ orders.add(o); 관계의경우에는해당엔티티에대해오퍼레이션 (persist, remove 등 ) 할때관계된엔티티도같이 persist, remove 될것인지를지정하는 cascade 타입을지정할수있다. 기본적으로는 cascade 값이지정되지않지만위와같이지정하면 Customer 엔티티가생성, 삭제될때관계된 Orders 엔티티도같이생성, 삭제된다. 쿼리 (Query) 자바퍼시스턴스에서는자바퍼시스턴스쿼리랭귀지 (Java Persistence Query Language) 라고하는객체지향적인쿼리언어를제공한다. EJBQL와유사하지만자바퍼시스턴스쿼리는 Bulk Update, Delete 문뿐만아니라서브쿼리 (Subquery) 도지원하므로그활용범위가넓다. 또한 DB에특화된 SQL 문을직접사용할수있는네이티브쿼리 (Native Query) 도지원한다. 쿼리를실제로사용할때는동적으로바로생성해서사용하거나네임드쿼리 (Named query) 라고하는정적인방식으로사용할수있다. // 동적쿼리 Query query = em.createquery( select c from Customer c where name=:name ); query.setparameter( name, name); List list = query.getresultlist(); // 정적쿼리 - @NamedQuery는엔티티에명기 @NamedQuery(name= findcustomerbyname, query= select c from Customer c where c.name=:name ) Query query = em.createnamedquery( findcustomerbyname ); query.setparameter( name, name); List list = query.getresultlist(); 네임드쿼리 (Named Query) 는엔티티클래스에직접명기되며재활용되기때문에해당엔티티에대해서자주활용되는쿼리의경우미리정의해놓고사용하면편리하다. 엔티티의라이프사이클과퍼시스턴스컨텍스트 EntityManager의동작은퍼시스턴스컨텍스트 (Persistence Context) 와이와관련된엔티티인스턴스의라이프사이클을알아야이해할수있다. 이부분은자바퍼시스턴스에서중요하고도조금복잡한부분이다. 퍼시스턴스컨텍스트 (Persistence Context) 는현재관리되고있는엔티티인스턴스들의집합을말하는데엔티티인스턴스의상태는 EntityManager 오퍼레이션과밀접한관련이있다. < 그림 2> 는엔티티의상태가어떻게변화하는지보여준다. Update tx commit/rollback persist clear New Managed Detached merge remove Removed < 그림 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 micro software 2006+7
DataSource 설정등과같이퍼시스턴스유닛 (Persistence Unit_ 에대한정보를담고있다. 퍼시스턴스유닛은엔티티를묶는단위이며이것은하나의 DB에만매핑된다. 퍼시스턴스유닛은유닛이름 (Unit Name) 식별자를가지는데이식별자는 EntityManager 나 EntityManagerFactory를얻을때사용된다. EntityManager em = emf.createentitymanager(); EntityTransaction tx = em.gettransaction(); tx.begin(); em.persist(customer); tx.commit(); // persistence.xml <persistence version= 1.0 xmlns= http://java.sun.com/xml/ns/ em.close(); emf.close(); 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> 자바SE에서는 javax.persistence.spi.persistence 부트스트랩 API를사용해서 EntityManagerFactory를얻는다는것과 EntityTransaction API를이용하여직접트랜잭션컨트롤을해주어야한다는차이가있다. 또한 persistence.xml 파일의위치는클래스패스상에서 META-INF/persistence.xml에있어야하며, 자바EE 환경과달리다음과같이모든엔티티클래스를명시적으로나열해주어야한다. </persistence> <persistence-unit name= orderunit > 패키징은다음과같이 JAR 파일이나디렉토리에할수있으며이단위를퍼시스턴스유닛루트 (Persistence Unit Root) 라고한다. <class>orderapp.entities.customer</class> <class>orderapp.entities.item</class> <class>orderapp.entities.orders</class> 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) 지금까지 EJB 3.0과자바퍼시스턴스 API 1.0 내용과예제코드를살펴보았다. 지면관계로많은내용을깊이있게다루지는못하였지만전반적인특징을살펴보는데도움이되었으리라믿는다. 2006년자바원 (JavaOne) 에서도 EJB 3.0과자바퍼시스턴스에관련된세션이많았다. 필자의블로그에가면세션에대한소개와프리젠테이션 PDF를받을수있다 (http://weblogs.java.net/blog/guruwons/archive/2006/06/ejb_30_sessions.ht ml). 자바SE 환경에서사용하기자바SE 환경에서사용할때는 Application-managed EntityManager만사용할수있으며 JDBC 드라이버에국한된로컬 (resource-local) 트랜잭션만쓸수있다. EntityManagerFactory emf = Persistence.createEntityManager Factory( orderunit ); 이달의디스켓 : EJB30samp.zip 참고자료 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 for Entity Persistence - http://java.sun.com/developer/technicalarticles/j2ee/jpa/ 3. JSR 220 - http://jcp.org/en/jsr/detail?id=220 micro software 2006+7