App Engine에 JDO 3.0 사용

JDO(자바 데이터 객체)는 자바에서 데이터베이스에 액세스하기 위한 표준 인터페이스로, 자바 클래스와 데이터베이스 테이블 간에 매핑을 제공합니다. JDO를 Datastore와 함께 사용하기 위한 오픈소스 플러그인이 있으며, 이 페이지에서는 해당 플러그인을 시작하는 방법에 대한 정보를 제공합니다.

경고: 대부분의 개발자는 낮은 수준의 Datastore API 또는 Datastore 전용으로 개발된 오픈소스 API 중 하나(예: Objectify)를 사용하는 것이 더 좋습니다. JDO는 기존 관계형 데이터베이스에 사용하도록 설계되었기 때문에 항목 그룹이나 상위 쿼리 등 관계형 데이터베이스와 차별화되는 Datastore의 몇 가지 측면을 명시적으로 나타낼 수 있는 방법이 없습니다. 이로 인해 이해하고 해결하기 어려운 미묘한 문제가 발생할 수 있습니다.

App Engine 자바 SDK에는 App Engine용 DataNucleus 플러그인의 버전 2.x가 포함됩니다. 이 플러그인은 JDO 3.0을 통해 App Engine Datastore를 사용할 수 있도록 해주는 DataNucleus Access Platform의 버전 3.0에 상응합니다.

JDO에 대한 자세한 내용은 Access Platform 3.0 문서를 참조하세요. 특히 JDO 매핑JDO API를 참조하세요.

경고: App Engine용 DataNucleus 플러그인의 버전 2.x는 DataNucleus v3.x를 사용합니다. 이 새로운 플러그인은 이전(1.x) 플러그인과 완전히 호환되지는 않습니다. 새 버전으로 업그레이드하는 경우 애플리케이션을 업데이트하고 테스트해야 합니다.

JDO 2.x 및 3.0을 지원하는 빌드 도구

Maven을 사용하면 App Engine용 DataNucleus 플러그인 2.x 또는 3.0 버전을 사용할 수 있습니다.

  • Maven 사용자: pom.xml 파일에서 다음 구성으로 클래스를 향상시킵니다.
    <plugin>
        <groupId>org.datanucleus</groupId>
        <artifactId>maven-datanucleus-plugin</artifactId>
        <version>3.2.0-m1</version>
        <configuration>
            <api>JDO</api>
            <props>${basedir}/datanucleus.properties</props>
            <verbose>true</verbose>
            <enhancerName>ASM</enhancerName>
        </configuration>
        <executions>
            <execution>
                <phase>process-classes</phase>
                <goals>
                    <goal>enhance</goal>
                </goals>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>org.datanucleus</groupId>
                <artifactId>datanucleus-core</artifactId>
                <version>3.1.3</version>
            </dependency>
        </dependencies>
    </plugin>

DataNucleus 플러그인 버전 2.x로 이전

이 섹션에서는 DataNucleus Access Platform 3.0 및 JDO 3.0에 상응하는 App Engine용 DataNucleus의 버전 2.x를 사용하도록 앱을 업그레이드하는 방법을 안내합니다. 이 플러그인은 1.x 플러그인과 완전히 호환되지는 않으며 변경될 수 있습니다. 업그레이드하는 경우 애플리케이션 코드를 업데이트하고 테스트해야 합니다.

새로운 기본 동작

App Engine DataNucleus 플러그인의 버전 2.x에서는 몇 가지 기본값이 이전 버전과 다릅니다.

  • 트랜잭션이 아닌 PersistenceManager.makePersistent(), PersistenceManager.deletePersistent() 호출은 이제 원자적으로 실행됩니다. 이전에는 이러한 함수는 다음 트랜잭션이나 PersistenceManager.close() 시 실행되었습니다.
  • 이제 더 이상 중복 PersistenceManagerFactory(PMF) 할당에 대한 예외가 없습니다. 대신 지속성 속성 datanucleus.singletonPMFForNametrue로 설정하면 해당 이름에 현재 할당된 싱글톤 PMF가 반환됩니다.
  • 이제 미소유 관계가 지원됩니다. 미소유 관계를 참조하세요.

구성 파일 변경사항

애플리케이션을 App Engine DataNucleus 플러그인 버전 2.x로 업그레이드하려면 build.xmljdoconfig.xml에서 구성 설정을 변경해야 합니다.

경고! 구성을 업데이트한 후에는 이전 버전과 호환되는지를 확인하기 위해 애플리케이션 코드를 테스트해야 합니다. 새 애플리케이션을 설정하고 있으며 최선 버전의 플러그인을 사용하려면 JDO 3.0 설정을 진행하세요.

  1. PersistenceManagerFactoryClass 속성이 변경되었습니다. jdoconfig.xml에서 이 줄을 변경합니다.

    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>

    ~
    <property name="javax.jdo.PersistenceManagerFactoryClass"
              value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

build.xml

DataNucleus 2.x에 맞게 copyjars 타겟을 변경해야 합니다.

  1. copyjars 타겟이 변경되었습니다. 다음 섹션을 업데이트하세요.
    <target name="copyjars"
        description="Copies the App Engine JARs to the WAR.">
      <mkdir dir="war/WEB-INF/lib" />
      <copy
          todir="war/WEB-INF/lib"
          flatten="true">
        <fileset dir="${sdk.dir}/lib/user">
          <include name="**/*.jar" />
        </fileset>
      </copy>
    </target>
    다음으로 업데이트합니다.
    <target name="copyjars"
        description="Copies the App Engine JARs to the WAR.">
      <mkdir dir="war/WEB-INF/lib" />
      <copy
          todir="war/WEB-INF/lib"
          flatten="true">
        <fileset dir="${sdk.dir}/lib/user">
            <include name="**/appengine-api-1.0-sdk*.jar" />
        </fileset>
        <fileset dir="${sdk.dir}/lib/opt/user">
          <include name="appengine-api-labs/v1/*.jar" />
          <include name="jsr107/v1/*.jar" />
          <include name="datanucleus/v2/*.jar" />
        </fileset>
      </copy>
    </target>
  2. datanucleusenhance 타겟이 변경되었습니다. 다음 섹션을 업데이트하세요.
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    다음으로 업데이트합니다.
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
        <enhance_war war="war">
                <args>
                <arg value="-enhancerVersion"/>
                <arg value="v2"/>
            </args>
        </enhance_war>
    </target>

JDO 3.0 설정

JDO를 사용하여 Datastore에 액세스하려면 App Engine 앱에 다음이 필요합니다.

  • JDO 및 DataNucleus 플러그인의 JAR은 앱의 war/WEB-INF/lib/ 디렉터리에 있어야 합니다.
  • jdoconfig.xml이라는 구성 파일은 JDO에 App Engine Datastore를 사용하라고 지시하는 구성과 함께 앱의 war/WEB-INF/classes/META-INF/ 디렉터리에 있어야 합니다.
  • 프로젝트의 빌드 프로세스가 컴파일된 데이터 클래스에서 컴파일 후 '보정' 단계를 수행하여 컴파일된 데이터를 JDO 구현에 연결해야 합니다.

JAR 복사

JDO 및 Datastore JAR은 App Engine 자바 SDK에 포함되어 있습니다. appengine-java-sdk/lib/opt/user/datanucleus/v2/ 디렉터리에서 찾을 수 있습니다.

JAR을 애플리케이션의 war/WEB-INF/lib/ 디렉터리에 복사합니다.

appengine-api.jarwar/WEB-INF/lib/ 디렉터리에 있는지 확인합니다. 프로젝트를 만들 때 이미 복사했을 수도 있습니다. App Engine DataNucleus 플러그인은 이 JAR을 사용하여 Datastore에 액세스합니다.

jdoconfig.xml 파일 만들기

JDO 인터페이스에는 애플리케이션의 war/WEB-INF/classes/META-INF/ 디렉터리에 jdoconfig.xml이라는 구성 파일이 필요합니다. 이 위치에서 이 파일을 직접 만들거나 빌드 프로세스를 통해 이 파일을 소스 디렉터리에서 복사할 수 있습니다.

다음과 같은 내용으로 파일을 만듭니다.

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
        <property name="datanucleus.appengine.singletonPMFForName" value="true"/>
    </persistence-manager-factory>
</jdoconfig>

Datastore 읽기 정책 및 호출 기한 설정

Datastore 쿼리 페이지에 설명된 대로 읽기 정책(strong consistency와 eventual consistency)과 호출 기한을 설정하여 Datastore의 동작을 맞춤설정할 수 있습니다. JDO에서는 jdoconfig.xml 파일의 <persistence-manager-factory> 요소에 원하는 값을 지정하여 이 작업을 수행합니다. 특정 PersistenceManager 인스턴스를 통해 실행되는 모든 호출은 PersistenceManagerFactory를 통해 관리자가 생성될 때 효과적인 구성 값을 사용합니다. 단일 Query 객체에 이 설정을 재정의할 수도 있습니다.

PersistenceManagerFactory에 읽기 정책을 설정하려면 datanucleus.appengine.datastoreReadConsistency이라는 속성을 포함합니다. 가능한 값은 EVENTUALSTRONG입니다. 지정하지 않으면 기본값은 STRONG입니다. 하지만 이 설정은 특정 항목 그룹 내 상위 쿼리에만 적용됩니다. 상위 쿼리가 아닌 쿼리는 적용되는 읽기 정책에 관계없이 항상 최종 일관성을 띱니다.)

<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

읽기 및 쓰기에 대해 별도의 데이터 저장소 호출 기한을 설정할 수 있습니다. 읽기의 경우 JDO 표준 속성 javax.jdo.option.DatastoreReadTimeoutMillis를 사용합니다. 쓰기의 경우 javax.jdo.option.DatastoreWriteTimeoutMillis을 사용합니다. 값은 밀리초 단위의 시간입니다.

<property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
<property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />

XG(교차 그룹) 트랜잭션을 사용하려면 다음 속성을 추가합니다.

<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />

같은 jdoconfig.xml 파일에서 서로 다른 name 속성을 사용하는 <persistence-manager-factory> 요소 여러 개가 동일한 앱에서 서로 다른 구성으로 PersistenceManager 인스턴스를 사용하도록 할 수 있습니다. 예를 들어 다음 jdoconfig.xml 파일은 구성 세트 두 개를 설정합니다. 하나는 "transactions-optional"이고, 다른 하나는 "eventual-reads-short-deadlines"입니다.

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
    </persistence-manager-factory>

    <persistence-manager-factory name="eventual-reads-short-deadlines">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>

        <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
        <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />
        <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />
        <property name="datanucleus.singletonPMFForName" value="true" />
    </persistence-manager-factory>
</jdoconfig>

이름이 지정된 구성 세트로 PersistenceManager를 만드는 방법에 대한 내용은 아래의 PersistenceManager 인스턴스 가져오기를 참조하세요.

데이터 클래스 향상

JDO는 빌드 프로세스에서 컴파일 후 '보정' 단계를 사용하여 데이터 클래스를 JDO 구현에 연결합니다.

명령줄에서 다음 명령어를 사용하여 컴파일된 클래스에서 향상 단계를 수행할 수 있습니다.

java -cp classpath com.google.appengine.tools.enhancer.Enhance
class-files

classpath에는 appengine-java-sdk/lib/ 디렉터리에 있는 JAR appengine-tools-api.jar 및 모든 데이터 클래스가 포함되어야 합니다.

DataNucleus 바이트코드 보정기에 대한 자세한 내용은 DataNucleus 문서를 참조하세요.

PersistenceManager 인스턴스 가져오기

앱은 PersistenceManager 클래스의 인스턴스를 사용하여 JDO와 상호작용합니다. PersistenceManagerFactory 클래스의 인스턴스에서 메서드를 인스턴스화하고 호출하여 이 인스턴스를 가져옵니다. 팩토리는 JDO 구성을 사용하여 PersistenceManager 인스턴스를 만듭니다.

PersistenceManagerFactory 인스턴스는 초기화되는 데 시간이 다소 걸리므로 앱은 단일 인스턴스를 재사용해야 합니다. PersistenceManagerFactory 인스턴스를 관리하는 쉬운 방법은 다음과 같이 정적 인스턴스가 있는 싱글톤 래퍼 클래스를 만드는 것입니다.

PMF.java

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

팁: "transactions-optional"jdoconfig.xml 파일에 있는 구성 세트의 이름을 나타냅니다. 앱에서 여러 구성 세트를 사용하는 경우에는 원하는 JDOHelper.getPersistenceManagerFactory()를 호출하도록 이 코드를 확장해야 합니다. 코드는 각 PersistenceManagerFactory의 싱글톤 인스턴스를 캐시해야 합니다.

앱은 팩토리 인스턴스를 사용하여 Datastore에 액세스하는 요청마다 하나의 PersistenceManager 인스턴스를 만듭니다.

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import PMF;

// ...
    PersistenceManager pm = PMF.get().getPersistenceManager();

PersistenceManager를 사용하여 데이터 객체를 저장, 업데이트, 삭제하고 Datastore 쿼리를 수행합니다.

PersistenceManager 인스턴스를 마쳤으면 close() 메서드를 호출해야 합니다. close() 메서드를 호출한 후에 PersistenceManager 인스턴스를 사용하면 오류가 발생합니다.

try {
    // ... do stuff with pm ...
} finally {
    pm.close();
}

JDO 3.0의 지원되지 않는 기능

JDO 인터페이스의 다음 기능은 App Engine 구현에서 지원되지 않습니다.

  • 다대다 소유 관계
  • 'join' 쿼리. 상위 종류를 대상으로 쿼리를 실행할 때 필터에 하위 항목의 필드를 사용할 수 없습니다. 쿼리에서 키를 사용하여 상위 요소의 관계 필드를 직접 테스트할 수는 있습니다.
  • JDOQL 그룹화 및 기타 집계 쿼리
  • 다형성 쿼리. 클래스 쿼리를 실행하여 서브클래스의 인스턴스를 가져올 수 없습니다. 각 클래스는 Datastore에서 별도의 항목 종류로 표시됩니다.

트랜잭션 중지 및 기존 JDO 앱 포팅

사용이 권장되는 JDO 구성은 datanucleus.appengine.autoCreateDatastoreTxns라는 속성을 true로 설정합니다. 이는 Datastore 트랜잭션을 애플리케이션 코드에서 관리되는 JDO 트랜잭션에 연결할 것을 JDO 구현에 지시하는 App Engine 고유 속성입니다. 처음부터 새로 앱을 빌드하는 경우 이 속성을 사용하는 것이 좋습니다. 하지만 기존의 JDO 기반 애플리케이션을 App Engine에서 실행하려는 경우 이 속성 값을 false로 설정하는 대체 지속성 구성을 사용하려 할 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="false"/>
    </persistence-manager-factory>
</jdoconfig>

이것이 유용할 수 있는 이유를 이해하려면 트랜잭션 내에서 같은 항목 그룹에 속하는 객체만 사용할 수 있다는 점을 기억해야 합니다. 기존 데이터베이스를 사용하여 빌드한 애플리케이션은 일반적으로 글로벌 트랜잭션을 사용할 수 있다고 가정하기 때문에 트랜잭션 내의 모든 레코드 집합을 업데이트할 수 있습니다. App Engine Datastore는 글로벌 트랜잭션을 지원하지 않기 때문에 코드가 글로벌 트랜잭션을 사용할 수 있다고 가정할 경우 App Engine에서 예외가 발생합니다. 매우 커질 수 있는 코드베이스를 살펴보고 모든 트랜잭션 관리 코드를 삭제하는 대신 간단히 Datastore 트랜잭션을 사용 중지할 수 있습니다. 이렇게 해도 다중 레코드 수정의 원자성에 대한 코드의 가정을 처리하는 데 아무런 도움도 되지 않지만 앱이 제대로 작동되므로 트랜잭션 코드를 한 번에 리팩토링하는 대신 필요에 따라 증분 방식으로 리팩토링할 수 있게 됩니다.