Java Data Object
Version 1.0
소개
엔터프라이즈 어플리케이션을 위한 퍼시스턴트 도메인 모델(persistent domain model)을 개발하는 것은 쉽지 않다. 우리가 도메인 모델을 가지고 비즈니스 로직을 구현하는 이유는 문제 도메인이 복잡하기 때문이다. 따라서 쉽고 빠르게 변경해서 유닛 테스트(unit test)를 하는 것이 중요하다. 그러나 빈번하게 퍼시스턴트(persistence)를 개발하는 기술은 개발 속도를 더디게 할 수 있다. 왜냐하면 이는 코드와 어플리케이션, 데이터베이스서버 간의 결합도를 높이기 때문이다. 이는 수정-컴파일-실행-디버그를 늘려서 테스트 위주의 개발(test-driven development) 을 더 어렵게 한다. 이 글은 어떻게 Java Data Objects(JDO)[JDO]가 이와 같은 문제를 해결하고 개발의 속도를 향상시킬 수 있는지에 대해 설명하고 있다.
오직 어플리케이션 서버 안에서만 사용될 수 있는 퍼시스턴트 기술들은 개발 속도를 느리게 한다. 왜냐하면 이러한 기술들은 개발자가 변경사항을 원할 때 마다 어플리케이션 서버 로 컴포넌트들을 다시 디플로이(redeploy) 해야 하기 때문이다. 결국 이것은 시간이 많이 소모 되는 작업들 이다. 이와 유사하게 도메인 모델 코드와 데이터베이스를 결합하는 퍼시스턴트 기술들 역시 개발속도를 느리게 한다. 개발자들은 변경된 사항을 테스트 하기 전에, 그들이 수정한 데이터베이스 스키마와 다시 빌드한 데이터베이스가 적용된 도메인 모델을 매번 갱신 하게 해야 한다. 더욱이 테스트들은 데이터베이스와 관련이 되었을 때 일반적으로 10배 이상 느려진다.
TheServerSide에 발표된 이전 글은 퍼시스턴트를 제공하는 엔티티빈(entity beans)으로 부터 비즈니스 로직을 분리할 수 있는 두 레벨의 도메인 모델 디자인에 대해 언급 했었다. 이것은 도메인 모델이 데이터베이스 없이 EJB 컨테이너 밖에서 테스트 될 수 있고 이러한 것들은 결국 개발을 빨리 하게 한다는 것이었다. 그러나, 강하게 언급 된 몇 개의 부분은 엔티티빈(entity beans)을 대신하여 Java Data Objects(JDO)[JDO]를 사용하는 쉽고 간단한 접근 이었다. 그리고 JDO를 사용하는 것에 대해 강조하는 것 같아 보였다. JDO는 데이터베이스 없이 도메인 모델을 테스트 하고 개발을 가능하게 하는 Plain Old Java Objects (POJO)을 위한 투명한 퍼시스턴트를 제공해 준다. JDO 어플리케이션은 어플리케이션 서버 밖에서 실행 될 수 있고 이것은 J2EE 컴포넌트 디플로이 과정을 생략 함으로써 수정-컴파일-실행 사이클을 빠르게 할 수 있다. 그러나 좋지 않은 사항들은 항상 상세한 것 안에 존재 하기 때문에, 예제 어플리케이션의 JDO 버전을 구현 하였던 필자의 주장들을 테스트 하도록 하겠다.
살펴보기
그림 1은 Process Order 유스케이스(use case)를 구현하는 주요 클래스들을 나타낸다.
ProcessOrderService는 POJO에 의해 구현 된 도메인 모델을 은닉화한 세션 퍼사드 [EJBPATTERNS]이다. 이것은 유스케이스의 스텝과 이에 상응 하는 메소드들을 정의한 무상태세션빈(stateless session bean)이다. 메소드의 파라미터들과 리턴값들은 원시 자바타입이고 데이터 운반 오브젝트(DTO)[EJBPATTERNS]들은 이 다이어그램에 나타나지 않는다. 예를 들어 메소드 사용자가 배달 정보가 들어왔을 때 프리젠테이션 티어에 의해 호출이 되는 enterDeliveryAddress()는 pending order id, address 와 같은 파라미터를 가지고 있다. 이것은 상태 코드들로 구성된 EnterDeliveryAddressAndTimeResponse와 PendingOrder 안에서 값들의 스냅샷(snapshot)인 PendingOrderValue와 line items과 restaurant을 포함한 관련된 오브젝트를 반환한다.
그림 1 - Session facade and domain model classes
PendingOrder와 그것의 line items, payment information, delivery address, time은 Process Order 유스케이스에 대한 세션상태를 구성한다. 사용자가 유스케이스를 종료하였을 때 그들이 입력한 정보는 PendingOrder에 저장되어있다. 이 어플리케이션에서 우리는 서버를 상태가 없게 만들기 위하여 데이터베이스 안에 세션 상태를 저장한다. (무상태세션빈을 사용한다는 말)
Restaurant, menu items, opening times은 어플리케이션을 위해 레코드 데이터를 구성한다. 이 레코드 데이터는 자연스럽게 데이터베이스에 저장이 된다. 도메인 모델은 역시 몇 개의 저장소를 가지고 이것은 퍼시스턴트 저장소에 대한 접근을 은닉화 한다.
RestaurantRepository는 PendingOrders를 관리하고 RestaurantRepository는 Restaurants을 관리한다.
그림 2는 어플리케이션 클래스들이 JDO APIs을 사용할 수 있는 한가지 방법을 보여준다.
그림 2 - Invoking JDO APIs
도메인 모델에 있는 PendingOrder, PendingOrderLineItem, Restaurant, MenuItem 와 같은 클래스들의 대부분은 그들이 퍼시스턴트인지 알지 모른다. 따라서 JDO APIs를 호출하지 않는다. 그들은 모두 POJO 클래스들이고 개발자들은 어떻게 자신의 클래스와 필드가 데이터베이스에 있는 테이블과 컬럼에 매핑이 되는지를 표현한 XML 메타데이터를 작성한다. JDO구현은 클래스 파일 촉진 도구(class file enhancement tool)을 제공하는데 이것은 XML 메타데이터를 읽을 수 있고 클래스들이 퍼시스턴트가 가능하게 해 주기 위해 클래스 파일을 수정 할 수 있게 해 준다.
세션 퍼사드와 레포지토리들은 JDO APIs를 호출 할 수 있는 유일한 클래스들이다. 그들은 JDO에 의해 제공 받은 PersistenceManagerFactory, PersistenceManager ,Query interfaces를 사용한다. 어플리케이션은 JDO가 어플리케이션에게 제공하는 주요 클래스인 PersistenceManager를 획득하기 위하여 PersistenceManagerFactory를 이용한다. PersistenceManager는 getObjectById()를 포함하는데 이것은 id를 가지고 주어진 오브젝트 를 찾는데 이용되고, makePersistent()는 새로운 오브젝트 퍼시스턴트를 만들고, close()는 PersistenceManager를 닫은 역할을 한다. PersistenceManager는 Query 오브젝트를 위한 메소드를 주로 제공 하며 데이터 저장소에 대해 쿼리를 실행하기 위한 메소드들을 제공한다.
각 세션 퍼사드 비즈니스 메소드는 PersistenceManager와 Query interfaces를 이용함으로써 데이터베이스로부터 1개 이상의 오브젝트들을 로드하기 위한 레포지토리를 사용하고 간단한 다음 레퍼런스를 통해 관련된 오브젝트들을 네비게이션(navigation) 한다. 오브젝트들에게서 어떤 변화가 발생하면 자동적으로 데이터베이스에 반영이 된다. 특정 어플리케이션은 일반적인 방법인 new를 사용하여 오브젝트를 생성하고 PersistenceManager.makePersistent()를 호출하거나 또는 이미 만들어진 퍼시스턴트 오브젝트에 의해 오브젝트의 퍼시스턴트를 가능하게 한다.
JDO을 시작하기 앞서서
무료 JDO vender Fast Object설치하기 : Fast Object는 www.jdocenter.com사이트에서 회원가입을 하면 교육용에 한하여 설치 할수 있는 무료 프로그램을 다운 받을수 있다.
환경설정 : 프로그램 인스톨후 다음과 같이 CLASSPATH 및 PATH를 설정해준다. 다음은 디폴트 위치에 설치하였을 때 정보이다.
CLASSPATH=.;D:\FastObjects_j1\runtime\lib\FastObjects_j1_JDO.jar; D:\FastObjects_j1\runtime\lib\jdo.jar Path=.; D:\FastObjects_j1\bin |
간단한 예제
public class MyClass { private String name; // JDO에서는 디폴트 생성자가 꼭 있어야 한다. public MyClass() { } public MyClass(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } public String toString() { return "MyClass: " + getName(); } } import com.poet.jdo.*; import com.poet.jdo.admin.DatabaseAdministration; import javax.jdo.*; import java.util.Iterator; public class First { public static void main( String[] args ) { java.util.Properties pmfProps = new java.util.Properties(); /** * 벤더에서 구현한 PersistenceManagerFactory를 지정해준다. */ pmfProps.put( "javax.jdo.PersistenceManagerFactoryClass", "com.poet.jdo.PersistenceManagerFactories" ); /** * 벤더에서 구현한 데이터베이스를 지정해준다. */ pmfProps.put( "javax.jdo.option.ConnectionURL", "fastobjects://LOCAL/firstJDO.j1" ); /** * Persistence Manager Factory를 가져온다. */ PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory( pmfProps ); PersistenceManager pm = null; try { /** * Persistence Manager Factory로 부터 Persistence Manager를 가져온다. */ pm = pmf.getPersistenceManager(); } catch ( JDOUserException e ) { DatabaseAdministration.create(pmf.getConnectionURL(), null); pm = pmf.getPersistenceManager(); } // 트렌젝션을 Persistence Manager로 부터 가져온다. Transaction txn = pm.currentTransaction(); // 트렌젹션 시작을 선언한다. txn.begin(); // MyClass의 Persisitence을 생성하여 Persistence Manger에 등록한다. for(int i=0; args.length > i; i++) { pm.makePersistent( new MyClass(args[i]) ); } // 트렌젹션 종료를 선언한다. txn.commit(); // 여기서 부터는 등록된 Persistence들을 살펴본다. try { txn = pm.currentTransaction(); txn.begin(); /** * 첫번째 파라미터는 가져오려는 Persisitence의 클래스 타입을 말하고 * 두번째 파라미터는 subClass도 가져올지여부를 나타낸다. */ Extent ext = pm.getExtent( MyClass.class, true ); Iterator iter = ext.iterator(); while ( iter.hasNext() ) { System.out.println( ((MyClass)iter.next()).toString() ); } // 사용한 리소스를 풀어준다. // closeAll()을 호출해도 된다. ext.close( iter ); txn.rollback(); } catch ( JDOUserException e ) { System.out.println( e ); txn.rollback(); } pm.close(); } } //à MyClass.jdo 파일은 MyClass.class파일과 같은 위치에 둔다. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jdo SYSTEM "http://java.sun.com/dtd/jdo_1_0.dtd"> <jdo> <package name=""> <class name="MyClass"> </class> </package> </jdo> |
Javac –d ./ *.java 명령어도 컴파일 한다.
ptj –enhance 명령어도 jdo실행준비를 한다.
Java First inter999, inter100, inter101 을 입력한다.
D:\FastObjects_j1\Examples_JDO\Javac2\First>ptj -enhance FastObjects SDK Toolkit, Version 9.0.7.166. For non-commercial use only Copyright (C) 1996-2003 POET Software Corporation Info: Read metadata 'D:\FastObjects_j1\Examples_JDO\Javac2\First\MyClass.jdo' Info: Enhanced class '.\MyClass.class' D:\FastObjects_j1\Examples_JDO\Javac2\First>java First inter999 inter100 inter101 MyClass: inter999 MyClass: inter100 MyClass: inter101 |
ServiceLocator.java
package chap2; import java.util.*; import javax.jdo.*; import com.poet.jdo.admin.DatabaseAdministration; public class serviceLocator { private static HashMap cachProperty = new HashMap(); private static HashMap cachManager = new HashMap(); public static PersistenceManagerFactory getMyClassProperty() { if(!cachProperty.containsKey("MyClass")) { Properties prop = new Properties(); prop.put("javax.jdo.PersistenceManagerFactoryClass", "com.poet.jdo.PersistenceManagerFactories"); prop.put("javax.jdo.option.ConnectionURL", "fastobjects://LOCAL/firstJDO.j1"); cachProperty.put("MyClass",JDOHelper.getPersistenceManagerFactory(prop)); } return (PersistenceManagerFactory)cachProperty.get("MyClass"); } public static PersistenceManager getMyClassManager() { if(!cachManager.containsKey("MyClass")) { try { cachManager.put("MyClass",getMyClassProperty().getPersistenceManager()); } catch(JDOUserException e) { DatabaseAdministration.create(getMyClassProperty().getConnectionURL(), null); cachManager.put("MyClass",getMyClassProperty().getPersistenceManager()); } } return (PersistenceManager)cachManager.get("MyClass"); } } |
SelectExample.java
package chap2; import com.poet.jdo.*; import com.poet.jdo.admin.DatabaseAdministration; import javax.jdo.*; import java.util.*; import javax.jdo.PersistenceManagerFactory; import javax.jdo.PersistenceManager; public class SelectExample { public static void main(String[] args) { PersistenceManager pm = serviceLocator.getMyClassManager(); Transaction txn = null; try { txn = pm.currentTransaction(); txn.begin(); Extent ext = pm.getExtent(MyClass.class, true); Query query = pm.newQuery(); query.declareParameters("String name"); query.setFilter("this.name == name"); query.setCandidates(ext); Collection result = (Collection) query.execute(args[0]); Iterator itr = result.iterator(); while(itr.hasNext()) { MyClass myclass = (MyClass)itr.next(); System.out.println("Name = "+myclass.getName()); } query.close(result); txn.commit(); } catch(JDOUserException e) { System.out.println(e); txn.rollback(); } finally { pm.close(); } } } |
JDOQL의 다른 용법들..
// Employee 는 Person의 서브클래스이다. Extent ext = pm.getExtent(Person.class, true); Query qry = pm.newQuery(); qry.setCandidates(ext); qry.setClass(Employee.class); Collection res = (Collection) qry.execute(); // Company 클래스에는 employees 라는 Collection이 있다. Extent ext = pm.getExtent(Company.class, true); Query qry = pm.newQuery(); qry.declareVariables("Employee emp"); qry.setFilter("employees.contains(emp) & emp.name == \"Max\""); qry.setCandidates(ext); Collection res = (Collection) qry.execute(); |
Updating an Object
package chap2; import com.poet.jdo.*; import com.poet.jdo.admin.DatabaseAdministration; import javax.jdo.*; import java.util.*; import javax.jdo.PersistenceManagerFactory; import javax.jdo.PersistenceManager; public class UpdateExample { public static void main(String[] args) { if(args.length < 2) System.exit(0); PersistenceManager pm = serviceLocator.getMyClassManager(); Transaction txn = null; try { txn = pm.currentTransaction(); txn.begin(); Extent ext = pm.getExtent(MyClass.class, true); Query query = pm.newQuery(); query.declareParameters("String arg"); query.setFilter("this.name == arg"); query.setCandidates(ext); Collection result = (Collection) query.execute(args[0]); Iterator itr = result.iterator(); while(itr.hasNext()) { MyClass myclass = (MyClass)itr.next(); System.out.print("Name is "+myclass.getName()); myclass.setName(args[1]); System.out.println(" to Name is "+myclass.getName()); } query.close(result); txn.commit(); txn.rollback(); // 로백테스트 } catch(JDOUserException e) { System.out.println(e); txn.rollback(); } finally { pm.close(); } } } |
Deleting an Object
package chap2; import com.poet.jdo.*; import com.poet.jdo.admin.DatabaseAdministration; import javax.jdo.*; import java.util.*; import javax.jdo.PersistenceManagerFactory; import javax.jdo.PersistenceManager; public class DeleteExample { public static void main(String[] args) { if(args.length < 1) System.exit(-1); PersistenceManager pm = serviceLocator.getMyClassManager(); Transaction txn = null; try { txn = pm.currentTransaction(); txn.begin(); Extent ext = pm.getExtent(MyClass.class, true); Query query = pm.newQuery(); query.declareParameters("String arg"); query.setFilter("this.name == arg"); query.setCandidates(ext); Collection result = (Collection)query.execute(args[0]); pm.deletePersistentAll(result); query.close(result); txn.commit(); //txn.rollback(); } catch(JDOUserException e) { System.out.println(e); txn.rollback(); } finally { pm.close(); } } } |
Object 삭제에는 다음과 같은 3가지 Method가 있다.
public void deletePersistent(java.lang.Object pc)
public void deletePersistentAll(java.util.Collection pcs)
public void deletePersistentAll(java.lang.Object[] pcs)
Basic Types
Primitive Types |
Wrapper Classes |
Supported System Interfaces |
Supported System Classes |
boolean |
java.lang.Boolean |
java.util.Collection |
java.lang.String |
byte |
java.lang.Byte |
java.util.List |
java.math.BigDecimal |
char |
java.lang.Char |
java.util.Map |
java.math.BigInteger |
double |
java.lang.Bouble |
java.util.Set |
java.util.Date |
int |
java.lang.Integer |
javax.jdo.PersistenceCapable |
java.util.Locale |
float |
java.lang.Float |
|
java.util.ArrayList |
long |
java.lang.Long |
|
java.util.HashMap |
short |
java.lang.Short |
|
java.util.HashSet |
|
|
|
java.util.Hashtable |
|
|
|
java.util.LinkedList |
|
|
|
java.util.Map |
|
|
|
java.util.TreeMap |
|
|
|
java.util.TreeSet |
|
|
|
java.util.Vector |
Exception
JDOException 은 JDO에서 최상위 Exception이다. JDOException은 java.lang.RuntimeException을 상속받고있다.
다음은 Exception 전략의 한 예를 보여줍니다.
PersistenceManager pm = serviceLocator.getBookWithAuthorManager(); Author author = new Author(args[1]); Transaction txn = pm.currentTransaction(); txn.begin(); pm.makePersistent( new BookWithAuthor(args[0],author) ); txn.commit(); try { txn = pm.currentTransaction(); txn.begin(); Extent ext = pm.getExtent( BookWithAuthor.class, true ); Iterator iter = ext.iterator(); while ( iter.hasNext() ) { System.out.println( ((BookWithAuthor)iter.next()).toString() ); } ext.close( iter ); txn.commit(); } catch ( JDOException e ) { System.out.println( e ); txn.rollback(); } finally { if(pm != null && !pm.isClosed()) { if(pm.currentTransaction().isActive()) pm.currentTransaction().rollback(); pm.close(); } } |
Object Identity(객체 감정)
package chap4.model; import com.poet.jdo.*; import com.poet.jdo.admin.DatabaseAdministration; import javax.jdo.*; import java.util.Iterator; public class CreateBookWithAuthor { public static void main( String[] args ) { if(args.length <2) System.exit(-1); PersistenceManager pm = serviceLocator.getBookWithAuthorManager(); Author author = new Author(args[1]); Transaction txn = pm.currentTransaction(); txn.begin(); BookWithAuthor objectOriginal = new BookWithAuthor(args[0],author); pm.makePersistent(objectOriginal); txn.commit(); try { txn = pm.currentTransaction(); txn.begin(); Extent ext = pm.getExtent( BookWithAuthor.class, true ); Iterator iter = ext.iterator(); BookWithAuthor objectIternater = null; Object empIteraterId = null; Object empOriginalId = JDOHelper.getObjectId(objectOriginal); while ( iter.hasNext() ) { objectIternater = (BookWithAuthor)iter.next(); if(objectOriginal == objectIternater) System.out.println("Object is == : "+objectIternater.toString()); else System.out.println("Object is != : "+objectIternater.toString()); empIteraterId = JDOHelper.getObjectId(objectIternater); if(empOriginalId.equals(empIteraterId)) System.out.println("is Same Class"); } ext.close( iter ); txn.commit(); } catch ( JDOException e ) { System.out.println( e ); txn.rollback(); } finally { if(pm != null && !pm.isClosed()) { if(pm.currentTransaction().isActive()) pm.currentTransaction().rollback(); pm.close(); } } } } |
è output
java chap4.model.CreateBookWithAuthor inter10000 totoro2 Object is != : Book Name = javaInWorld, Author = Object is != : Book Name = JavaPerfact, Author = 후루꾸 Object is != : Book Name = JavaPerfact, Author = Object is != : Book Name = inter10000, Author = totoro Object is != : Book Name = inter10000, Author = totoro2 Object is == : Book Name = inter10000, Author = totoro2 is Same Class |