Java EE에는 2 개의 ORM 기술이 있습니다. JPA와 JDO입니다. JPA와 JDO는 ORM기술이라는 것이 공통점이지만 목표로하는 대상 저장소에 대해서 차이를 갖습니다. JPA는 RDBMS를 데이터 저장소로 전제합니다. 그러나 JDO는 RDBMS, 파일, 엑셀, Bigtable, HBase, LDAP등을 대상 저장소로 합니다.
Google App Engine은 데이터 Google DataStore에 저장하는 방식으로 Native API, JDO 그리고 JPA를 지원합니다. 구글은 JDO 구현체로 Data NUcleus Platform을 사용합니다. 이 3가지 데이터 저장 인터페이스은 다음과 같은 연관성을 갖습니다.
- Native API : 기존 C라이브러리 포팅
- JDO: Native API를 프록시하여 JDO 프로바이더 개발 by Data Nucleus
- JPA: 별도의 JPA 인터페이스가 개발된 것이라기 보다 JDO가 지원되면서 자연스럽게 지원
JDO Enhance
ORM 엔진 입장에서 가장 어려운 것은 영속성 대상 객체의 상태를 추적하는 것 입니다. 객체의 상태 추적은 가장 많은 자원을 소비할 뿐만 아니라 가장 복잡한 부분입니다.
JDO는 객체의 상태를 추적하는 것을 가볍고 쉽게 처리하기 위하여 영속성 객체 Enhancement를 사용합니다. 영속서 객체에 특정 멤버 변수와 함수를 추가하여 객체의 상태를 추적할 수 있는 방법을 제공하는 것 입니다. Hibernate 와 TopLink, JPA등이 런타임 프록시를 만들어 사용하는 방식을 사용한다면 JDO는 컴파일 시점에 코드를 삽입하는 방식을 사용하는 차이가 있습니다.
이렇게 JDO를 사용하기 위해서는 클래스 컴파일 이후에 코드를 변형하는 Enhance 작업을 수행해야 합니다. 결과적으로 JDO를 개발하기 위해서는 개발 툴 및 빌드 툴 (Ant & Maven)에 enhance를 설정할 수 있어야 합니다.
이클립스 enhance설정
Google은 Google App Engine을 지원하는 플러그인으로 Google Plugin을 제공합니다. 이 플러그인을 설정하고 구글 프로젝트를 생성할 경우에 Enhance는 기본적으로 빌드 프로세스에 설정 됩니다.
다음은 이클립스 구글 프로젝트의 빌드 속성입니다. 빌드 절차중에 Enhancer가 선택된것을 확인할 수 있습니다.
다음은 프로젝트 속성중 Google 프로젝트의 Enhance대상을 패턴으로 적용하는 설정입니다. 모든 클래스를 대상으로 할경우 컴파일 속도가 느려지는 문제가 발생할 수 있습니다. 이러한 불편을 최소화하기 위하여 Enhance대상이 되는 패키지 패턴을 등록하는 설정 창 입니다.
위와 같은 설정결과로 클래스 컴파일 후에는 다음과 같은 Enhance가 돌아가는 것을 확인 할 수 있습니다.
Maven Plugin의 Enhance 설정
프로젝트 관리 툴로 Maven을 사용할 경우 DataNucleus에서 제공하는 datanucleus plugin을 사용하게 됩니다. 이 플러그인을 사용할 경우 enhance를 구동하는 시점과 enhance 대상이 되는 클래스의 패키지를 패턴으로 지정할 수 있습니다. 다음은 컴파일 다음 단계에 model 패키지에 포함된 모든 클래스를 enhance하는 설정 입니다.
Ant에서 enhance 지정
Google App Engine SDK에서는 ant를 위한 설정을 지원합니다. 아래는 SDK를 이용하여 프로젝트 빌드 Ant을 적용한 예제 입니다. 아래 예제에서 "datanucleusenhance" 타스크의 빌드 설정은 다음과 같습니다.
JDO Enhance의 결과 산출물
위와 같은 방법으로 빌드설정을 마친 상태라면 (클래스 컴파일 후 Enhance 적용) JDO 엔티티 클래스는 컴파일 후 변경이 발생합니다. JDO 엔티티 클래스에 적용된 변화를 클래스 디컴파일을 통해서 확인 할 수 있습니다.
JDO Simple Entity Code
다음은 테스트로 작성한 JDO Entity 클래스 코드 입니다.
package artszen.gae.model;
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
@PersistenceCapable
public class Employee {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String title;
@Persistent
private String firstName;
@Persistent
private String lastName;
@Persistent
private Date hireDate;
public Employee() {
super();
// TODO Auto-generated constructor stub
}
public Employee(String title, String firstName, String lastName, Date hireDate) {
super();
this.title = title;
this.firstName = firstName;
this.lastName = lastName;
this.hireDate = hireDate;
}
public Key getKey() {
return key;
}
public void setKey(Key key) {
this.key = key;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Employee [key=" + key + ", title=" + title + ", firstName="
+ firstName + ", lastName=" + lastName + ", hireDate="
+ hireDate + "]";
}
}
Enhance 후 클래스 코드 상태: 디컴파일 결과
다음은 위 클래스 코드의 컴파일 및 enhance 후 클래스를 디컴파일한 결과 입니다. 아래 코드를 보면 많은 멤버변수와 클래스 메소드가 추
package artszen.gae.model;
import com.google.appengine.api.datastore.Key;
import java.util.Date;
import javax.jdo.JDOFatalInternalException;
import javax.jdo.PersistenceManager;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.identity.ObjectIdentity;
import javax.jdo.spi.JDOImplHelper;
import javax.jdo.spi.PersistenceCapable.ObjectIdFieldConsumer;
import javax.jdo.spi.PersistenceCapable.ObjectIdFieldSupplier;
import javax.jdo.spi.StateManager;
@javax.jdo.annotations.PersistenceCapable
public class Employee implements javax.jdo.spi.PersistenceCapable{
@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String title;
@Persistent
private String firstName;
@Persistent
private String lastName;
@Persistent
private Date hireDate;
protected transient StateManager jdoStateManager;
protected transient byte jdoFlags;
private static final byte[] jdoFieldFlags;
private static final Class jdoPersistenceCapableSuperclass;
private static final Class[] jdoFieldTypes;
private static final String[] jdoFieldNames = __jdoFieldNamesInit();
private static final int jdoInheritedFieldCount;
public Employee(){}
public Employee(String title, String firstName, String lastName, Date hireDate){
this.title = title;
this.firstName = firstName;
this.lastName = lastName;
this.hireDate = hireDate;
}
public Key getKey() {
return jdoGetkey(this);
}
public void setKey(Key key) {
jdoSetkey(this, key);
}
public String getFirstName() {
return jdoGetfirstName(this);
}
public void setFirstName(String firstName) {
jdoSetfirstName(this, firstName);
}
public String getLastName() {
return jdoGetlastName(this);
}
public void setLastName(String lastName) {
jdoSetlastName(this, lastName);
}
public Date getHireDate() {
return jdoGethireDate(this);
}
public void setHireDate(Date hireDate) {
jdoSethireDate(this, hireDate);
}
public String getTitle() {
return jdoGettitle(this);
}
public void setTitle(String title) {
jdoSettitle(this, title);
}
public String toString(){
return "Employee [key=" + jdoGetkey(this) + ", title=" + jdoGettitle(this) + ", firstName=" +
jdoGetfirstName(this) + ", lastName=" + jdoGetlastName(this) + ", hireDate=" +
jdoGethireDate(this) + "]";
}
static{
jdoFieldTypes = __jdoFieldTypesInit();
jdoFieldFlags = __jdoFieldFlagsInit();
jdoInheritedFieldCount = __jdoGetInheritedFieldCount();
jdoPersistenceCapableSuperclass = __jdoPersistenceCapableSuperclassInit();
JDOImplHelper.registerClass(___jdo$loadClass("swm.enterprise.rest.jdodemo.model.Employee"), jdoFieldNames, jdoFieldTypes, jdoFieldFlags, jdoPersistenceCapableSuperclass, new Employee());
}
public void jdoCopyKeyFieldsFromObjectId(PersistenceCapable.ObjectIdFieldConsumer fc, Object oid){
if (fc == null)
throw new IllegalArgumentException("ObjectIdFieldConsumer is null");
if (!(oid instanceof ObjectIdentity))
throw new ClassCastException("oid is not instanceof javax.jdo.identity.ObjectIdentity");
ObjectIdentity o = (ObjectIdentity)oid;
fc.storeObjectField(2, o.getKey());
}
protected void jdoCopyKeyFieldsFromObjectId(Object oid){
if (!(oid instanceof ObjectIdentity))
throw new ClassCastException("key class is not javax.jdo.identity.ObjectIdentity or null");
ObjectIdentity o = (ObjectIdentity)oid;
this.key = ((Key)o.getKey());
}
public final void jdoCopyKeyFieldsToObjectId(Object oid){
throw new JDOFatalInternalException("It's illegal to call jdoCopyKeyFieldsToObjectId for a class with SingleFieldIdentity.");
}
public final void jdoCopyKeyFieldsToObjectId(PersistenceCapable.ObjectIdFieldSupplier fs, Object paramObject)
{
throw new JDOFatalInternalException("It's illegal to call jdoCopyKeyFieldsToObjectId for a class with SingleFieldIdentity.");
}
public final Object jdoGetObjectId(){
if (this.jdoStateManager != null)
return this.jdoStateManager.getObjectId(this);
return null;
}
public final Object jdoGetVersion(){
if (this.jdoStateManager != null)
return this.jdoStateManager.getVersion(this);
return null;
}
protected final void jdoPreSerialize(){
if (this.jdoStateManager != null)
this.jdoStateManager.preSerialize(this);
}
public final PersistenceManager jdoGetPersistenceManager(){
return this.jdoStateManager != null ? this.jdoStateManager.getPersistenceManager(this) : null;
}
public final Object jdoGetTransactionalObjectId(){
return this.jdoStateManager != null ? this.jdoStateManager.getTransactionalObjectId(this) : null;
}
public final boolean jdoIsDeleted(){
return this.jdoStateManager != null ? this.jdoStateManager.isDeleted(this) : false;
}
public final boolean jdoIsDirty(){
if (this.jdoStateManager != null)
return this.jdoStateManager.isDirty(this);
return false;
}
public final boolean jdoIsNew(){
return this.jdoStateManager != null ? this.jdoStateManager.isNew(this) : false;
}
public final boolean jdoIsPersistent(){
return this.jdoStateManager != null ? this.jdoStateManager.isPersistent(this) : false;
}
public final boolean jdoIsTransactional(){
return this.jdoStateManager != null ? this.jdoStateManager.isTransactional(this) : false;
}
public void jdoMakeDirty(String fieldName){
if (this.jdoStateManager != null)
this.jdoStateManager.makeDirty(this, fieldName);
}
public final Object jdoNewObjectIdInstance(){
return new ObjectIdentity(getClass(), this.key);
}
public final Object jdoNewObjectIdInstance(Object key){
if (key == null)
throw new IllegalArgumentException("key is null");
if (!(key instanceof String))
return new ObjectIdentity(getClass(), key);
return new ObjectIdentity(getClass(), (String)key);
}
public final void jdoProvideFields(int[] indices){
if (indices == null)
throw new IllegalArgumentException("argment is null");
int i = indices.length - 1;
if (i >= 0)
do
{
jdoProvideField(indices[i]);
i--;
}
while (i >= 0);
}
public final void jdoReplaceFields(int[] indices){
if (indices == null)
throw new IllegalArgumentException("argument is null");
int i = indices.length;
if (i > 0){
int j = 0;
do{
jdoReplaceField(indices[j]);
j++;
}
while (j < i);
}
}
public final void jdoReplaceFlags(){
if (this.jdoStateManager != null)
this.jdoFlags = this.jdoStateManager.replacingFlags(this);
}
public final synchronized void jdoReplaceStateManager(StateManager sm){
if (this.jdoStateManager != null){
this.jdoStateManager = this.jdoStateManager.replacingStateManager(this, sm);
}
else{
JDOImplHelper.checkAuthorizedStateManager(sm);
this.jdoStateManager = sm;
this.jdoFlags = 1;
}
}
public boolean jdoIsDetached(){
return false;
}
public javax.jdo.spi.PersistenceCapable jdoNewInstance(StateManager sm){
Employee result = new Employee();
result.jdoFlags = 1;
result.jdoStateManager = sm;
return result;
}
public javax.jdo.spi.PersistenceCapable jdoNewInstance(StateManager sm, Object obj){
Employee result = new Employee();
result.jdoFlags = 1;
result.jdoStateManager = sm;
result.jdoCopyKeyFieldsFromObjectId(obj);
return result;
}
public void jdoReplaceField(int index){
if (this.jdoStateManager == null)
throw new IllegalStateException("state manager is null");
switch (index){
case 0:
this.firstName = this.jdoStateManager.replacingStringField(this, index);
break;
case 1:
this.hireDate = ((Date)this.jdoStateManager.replacingObjectField(this, index));
break;
case 2:
this.key = ((Key)this.jdoStateManager.replacingObjectField(this, index));
break;
case 3:
this.lastName = this.jdoStateManager.replacingStringField(this, index);
break;
case 4:
this.title = this.jdoStateManager.replacingStringField(this, index);
break;
default:
throw new IllegalArgumentException("out of field index :" + index);
}
}
public void jdoProvideField(int index){
if (this.jdoStateManager == null)
throw new IllegalStateException("state manager is null");
switch (index){
case 0:
this.jdoStateManager.providedStringField(this, index, this.firstName);
break;
case 1:
this.jdoStateManager.providedObjectField(this, index, this.hireDate);
break;
case 2:
this.jdoStateManager.providedObjectField(this, index, this.key);
break;
case 3:
this.jdoStateManager.providedStringField(this, index, this.lastName);
break;
case 4:
this.jdoStateManager.providedStringField(this, index, this.title);
break;
default:
throw new IllegalArgumentException("out of field index :" + index);
}
}
protected final void jdoCopyField(Employee obj, int index){
switch (index)
{
case 0:
this.firstName = obj.firstName;
break;
case 1:
this.hireDate = obj.hireDate;
break;
case 2:
this.key = obj.key;
break;
case 3:
this.lastName = obj.lastName;
break;
case 4:
this.title = obj.title;
break;
default:
throw new IllegalArgumentException("out of field index :" + index);
}
}
public void jdoCopyFields(Object obj, int[] indices){
if (this.jdoStateManager == null)
throw new IllegalStateException("state manager is null");
if (indices == null)
throw new IllegalStateException("fieldNumbers is null");
if (!(obj instanceof Employee))
throw new IllegalArgumentException("object is not an object of type swm.enterprise.rest.jdodemo.model.Employee");
Employee other = (Employee)obj;
if (this.jdoStateManager != other.jdoStateManager)
throw new IllegalArgumentException("state managers do not match");
int i = indices.length - 1;
if (i >= 0)
do{
jdoCopyField(other, indices[i]);
i--;
}
while (i >= 0);
}
private static final String[] __jdoFieldNamesInit(){
return new String[] { "firstName", "hireDate", "key", "lastName", "title" };
}
private static final Class[] __jdoFieldTypesInit(){
return new Class[] { ___jdo$loadClass("java.lang.String"), ___jdo$loadClass("java.util.Date"),
___jdo$loadClass("com.google.appengine.api.datastore.Key"),
___jdo$loadClass("java.lang.String"), ___jdo$loadClass("java.lang.String") };
}
private static final byte[] __jdoFieldFlagsInit(){
return new byte[] { 21, 21, 24, 21, 21 };
}
protected static int __jdoGetInheritedFieldCount(){
return 0;
}
protected static int jdoGetManagedFieldCount(){
return 5;
}
private static Class __jdoPersistenceCapableSuperclassInit(){
return null;
}
public static Class ___jdo$loadClass(String className){
try {
return Class.forName(className); } catch (ClassNotFoundException e) { }
throw new NoClassDefFoundError(e.getMessage());
}
private Object jdoSuperClone() throws CloneNotSupportedException {
Employee o = (Employee)super.clone();
o.jdoFlags = 0;
o.jdoStateManager = null;
return o;
}
private static String jdoGetfirstName(Employee objPC) {
if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) && (!objPC.jdoStateManager.isLoaded(objPC, 0)))
return objPC.jdoStateManager.getStringField(objPC, 0, objPC.firstName);
return objPC.firstName;
}
private static void jdoSetfirstName(Employee objPC, String val) {
if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
objPC.jdoStateManager.setStringField(objPC, 0, objPC.firstName, val);
else
objPC.firstName = val;
}
private static Date jdoGethireDate(Employee objPC) {
if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) &&
(!objPC.jdoStateManager.isLoaded(objPC, 1)))
return (Date)objPC.jdoStateManager.getObjectField(objPC, 1, objPC.hireDate);
return objPC.hireDate;
}
private static void jdoSethireDate(Employee objPC, Date val) {
if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
objPC.jdoStateManager.setObjectField(objPC, 1, objPC.hireDate, val);
else
objPC.hireDate = val;
}
private static Key jdoGetkey(Employee objPC) {
return objPC.key;
}
private static void jdoSetkey(Employee objPC, Key val) {
if (objPC.jdoStateManager == null)
objPC.key = val;
else
objPC.jdoStateManager.setObjectField(objPC, 2, objPC.key, val);
}
private static String jdoGetlastName(Employee objPC) {
if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) &&
(!objPC.jdoStateManager.isLoaded(objPC, 3)))
return objPC.jdoStateManager.getStringField(objPC, 3, objPC.lastName);
return objPC.lastName;
}
private static void jdoSetlastName(Employee objPC, String val) {
if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
objPC.jdoStateManager.setStringField(objPC, 3, objPC.lastName, val);
else
objPC.lastName = val;
}
private static String jdoGettitle(Employee objPC) {
if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) &&
(!objPC.jdoStateManager.isLoaded(objPC, 4)))
return objPC.jdoStateManager.getStringField(objPC, 4, objPC.title);
return objPC.title;
}
private static void jdoSettitle(Employee objPC, String val) {
if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
objPC.jdoStateManager.setStringField(objPC, 4, objPC.title, val);
else
objPC.title = val;
}
}