Menggunakan JDO 3.0 dengan App Engine

Objek Data Java (JDO) adalah antarmuka standar untuk mengakses database di Java, yang menyediakan pemetaan antara class Java dan tabel database. Ada plugin open source yang tersedia untuk menggunakan JDO dengan Datastore, dan halaman ini memberikan informasi tentang cara memulainya.

Peringatan: Menurut kami, sebagian besar developer akan memiliki pengalaman yang lebih baik menggunakan Datastore API tingkat rendah, atau salah satu API open source yang dikembangkan khusus untuk Datastore, seperti Objectify. JDO dirancang untuk digunakan dengan database relasional tradisional, sehingga tidak memiliki cara untuk secara eksplisit mewakili beberapa aspek Datastore yang membuatnya berbeda dengan database relasional, seperti entity group dan kueri ancestor. Hal ini dapat menyebabkan masalah ringan yang sulit dipahami dan diperbaiki.

App Engine Java SDK menyertakan plugin DataNucleus versi 2.x untuk App Engine. Plugin ini sesuai dengan DataNucleus Access Platform versi 3.0, yang memungkinkan Anda menggunakan App Engine Datastore melalui JDO 3.0.

Lihat dokumentasi Access Platform 3.0 untuk informasi selengkapnya tentang JDO. Secara khusus, lihat JDO Mapping dan JDO API.

Peringatan: Plugin DataNucleus 2.x untuk App Engine menggunakan DataNucleus v3.x. Plugin baru ini tidak sepenuhnya kompatibel dengan versi lama dengan plugin (1.x) sebelumnya. Jika Anda mengupgrade ke versi baru, pastikan untuk mengupdate dan menguji aplikasi.

Alat build yang mendukung JDO 2.x dan 3.0

Anda dapat menggunakan Maven untuk menggunakan plugin DataNucleus versi 2.x atau 3.0 untuk App Engine:

  • Untuk pengguna Maven: Anda dapat meningkatkan class dengan konfigurasi berikut di file 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>

Bermigrasi ke Versi 2.x Plugin DataNucleus

Bagian ini memberikan petunjuk untuk mengupgrade aplikasi Anda agar dapat menggunakan plugin DataNucleus versi 2.x untuk App Engine, yang sesuai dengan DataNucleus Access Platform 3.0 dan JDO 3.0. Plugin ini tidak sepenuhnya kompatibel dengan versi lama dengan plugin 1.x dan dapat berubah. Jika Anda mengupgrade, pastikan untuk mengupdate dan menguji kode aplikasi.

Perilaku Default Baru

Plugin App Engine DataNucleus versi 2.x memiliki beberapa default yang berbeda dari versi sebelumnya:

  • Panggilan non-transaksi ke PersistenceManager.makePersistent(), dan PersistenceManager.deletePersistent() sekarang dijalankan secara atomik. (Fungsi ini sebelumnya dijalankan pada transaksi berikutnya atau pada PersistenceManager.close().)
  • Sekarang tidak ada lagi pengecualian pada alokasi PersistenceManagerFactory (PMF) duplikat. Namun, jika Anda memiliki properti persistensi datanucleus.singletonPMFForName yang ditetapkan ke true, properti tersebut akan menampilkan PMF singleton yang saat ini dialokasikan untuk nama tersebut.
  • Hubungan yang tidak dimiliki kini didukung. Lihat Hubungan yang Tidak Dimiliki.

Perubahan pada File Konfigurasi

Untuk mengupgrade aplikasi ke plugin App Engine DataNucleus versi 2.x, Anda perlu mengubah setelan konfigurasi di build.xml dan jdoconfig.xml.

Peringatan! Setelah mengupdate konfigurasi, Anda harus menguji kode aplikasi untuk memastikan kompatibilitas mundur. Jika Anda menyiapkan aplikasi baru dan ingin menggunakan plugin versi terbaru, lanjutkan ke Menyiapkan JDO 3.0.

  1. Properti PersistenceManagerFactoryClass telah berubah. Ubah baris ini di jdoconfig.xml:

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

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

Dalam build.xml

Target copyjars perlu berubah untuk mengakomodasi DataNucleus 2.x:

  1. Target copyjars telah diubah. Update bagian ini:
    <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>
    menjadi:
    <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. Target datanucleusenhance telah berubah. Update bagian ini:
    <target name="datanucleusenhance" depends="compile"
        description="Performs enhancement on compiled data classes.">
      <enhance_war war="war" />
    </target>
    menjadi:
    <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>

Menyiapkan JDO 3.0

Untuk menggunakan JDO guna mengakses datastore, aplikasi App Engine memerlukan hal berikut:

  • JAR untuk JDO dan plugin DataNucleus harus berada di direktori war/WEB-INF/lib/ aplikasi.
  • File konfigurasi bernama jdoconfig.xml harus ada di direktori war/WEB-INF/classes/META-INF/ aplikasi, dengan konfigurasi yang memberi tahu JDO untuk menggunakan datastore App Engine.
  • Proses build project harus melakukan langkah "peningkatan" pascakompilasi pada class data yang dikompilasi untuk mengaitkannya dengan implementasi JDO.

Menyalin JAR

JDO dan JAR datastore disertakan dengan App Engine Java SDK. Anda dapat menemukannya di direktori appengine-java-sdk/lib/opt/user/datanucleus/v2/.

Salin JAR ke direktori war/WEB-INF/lib/ aplikasi Anda.

Pastikan appengine-api.jar juga ada dalam direktori war/WEB-INF/lib/. (Anda mungkin sudah menyalin ini saat membuat project.) Plugin App Engine DataNucleus menggunakan JAR ini untuk mengakses datastore.

Membuat File jdoconfig.xml

Antarmuka JDO memerlukan file konfigurasi bernama jdoconfig.xml di direktori war/WEB-INF/classes/META-INF/ aplikasi. Anda dapat membuat file ini di lokasi ini secara langsung, atau meminta proses build menyalin file ini dari direktori sumber.

Buat file dengan konten berikut:

<?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>

Menetapkan Kebijakan Baca Datastore dan Batas Waktu Panggilan

Seperti yang dijelaskan di halaman Kueri Datastore, Anda dapat menyesuaikan perilaku Datastore dengan menetapkan kebijakan baca (konsistensi kuat versus konsistensi tertunda) dan batas waktu panggilan. Di JDO, Anda melakukannya dengan menentukan nilai yang diinginkan dalam elemen <persistence-manager-factory> dari file jdoconfig.xml. Semua panggilan yang dilakukan dengan instance PersistenceManager tertentu akan menggunakan nilai konfigurasi yang akan berlaku saat pengelola dibuat oleh PersistenceManagerFactory. Anda juga dapat mengganti setelan ini untuk satu objek Query.

Untuk menetapkan kebijakan baca untuk PersistenceManagerFactory, sertakan properti bernama datanucleus.appengine.datastoreReadConsistency. Nilai yang mungkin adalah EVENTUAL dan STRONG: jika tidak ditentukan, nilai defaultnya adalah STRONG. (Namun, perlu diperhatikan bahwa setelan ini hanya berlaku untuk kueri ancestor dalam entity group tertentu. Kueri non-ancestor selalu memiliki konsistensi tertunda, terlepas dari kebijakan baca yang berlaku.)

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

Anda dapat menetapkan batas waktu panggilan datastore terpisah untuk operasi baca dan tulis. Untuk operasi baca, gunakan properti standar JDO javax.jdo.option.DatastoreReadTimeoutMillis. Untuk operasi tulis, gunakan javax.jdo.option.DatastoreWriteTimeoutMillis. Nilainya adalah jumlah waktu, dalam milidetik.

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

Jika Anda ingin menggunakan transaksi lintas grup (XG), tambahkan properti berikut:

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

Anda dapat memiliki beberapa elemen <persistence-manager-factory> dalam file jdoconfig.xml yang sama, dengan menggunakan atribut name yang berbeda, untuk menggunakan instance PersistenceManager dengan konfigurasi yang berbeda di aplikasi yang sama. Misalnya, file jdoconfig.xml berikut membuat dua kumpulan konfigurasi, satu bernama "transactions-optional" dan lainnya bernama "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>

Lihat Mendapatkan Instance PersistenceManager di bawah untuk informasi tentang cara membuat PersistenceManager dengan set konfigurasi bernama.

Meningkatkan Class Data

JDO menggunakan langkah "peningkatan" pascakompilasi dalam proses build untuk mengaitkan class data dengan implementasi JDO.

Anda dapat melakukan langkah peningkatan pada class yang dikompilasi dari command line dengan perintah berikut:

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

classpath harus berisi JAR appengine-tools-api.jar dari direktori appengine-java-sdk/lib/, serta semua class data Anda.

Untuk mengetahui informasi selengkapnya tentang DataNucleus bytecode enhancer, lihat dokumentasi DataNucleus.

Mendapatkan Instance PersistenceManager

Aplikasi berinteraksi dengan JDO menggunakan instance class PersistenceManager. Anda mendapatkan instance ini dengan membuat instance dan memanggil metode pada instance class PersistenceManagerFactory. Factory menggunakan konfigurasi JDO untuk membuat instance PersistenceManager.

Karena instance PersistenceManagerFactory memerlukan waktu untuk diinisialisasi, aplikasi harus menggunakan kembali satu instance. Cara mudah untuk mengelola instance PersistenceManagerFactory adalah dengan membuat class wrapper singleton dengan instance statis, seperti berikut:

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;
    }
}

Tips: "transactions-optional" merujuk pada nama konfigurasi yang ditetapkan dalam file jdoconfig.xml. Jika aplikasi Anda menggunakan beberapa set konfigurasi, Anda harus memperluas kode ini untuk memanggil JDOHelper.getPersistenceManagerFactory() seperti yang diinginkan. Kode Anda harus meng-cache instance singleton setiap PersistenceManagerFactory.

Aplikasi menggunakan instance factory untuk membuat satu instance PersistenceManager untuk setiap permintaan yang mengakses datastore.

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

import PMF;

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

Anda menggunakan PersistenceManager untuk menyimpan, mengupdate, dan menghapus objek data, serta menjalankan kueri datastore.

Setelah selesai membuat instance PersistenceManager, Anda harus memanggil metode close(). Menggunakan instance PersistenceManager setelah memanggil metode close() merupakan kesalahan.

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

Fitur JDO 3.0 yang Tidak Didukung

Fitur antarmuka JDO berikut tidak didukung oleh implementasi App Engine:

  • Memiliki hubungan many-to-many.
  • Kueri "Gabung". Anda tidak dapat menggunakan kolom entity turunan di filter saat menjalankan kueri pada jenis induk. Perhatikan bahwa Anda dapat menguji kolom hubungan induk secara langsung dalam kueri menggunakan kunci.
  • Pengelompokan JDOQL dan kueri gabungan lainnya.
  • Kueri polimorf. Anda tidak dapat menjalankan kueri class untuk mendapatkan instance subclass. Setiap class diwakili oleh jenis entity terpisah di datastore.

Menonaktifkan Transaksi dan Porting Aplikasi JDO yang Ada

Konfigurasi JDO yang sebaiknya digunakan menetapkan properti bernama datanucleus.appengine.autoCreateDatastoreTxns ke true. Ini adalah properti khusus App Engine yang memberi tahu implementasi JDO untuk mengaitkan transaksi datastore dengan transaksi JDO yang dikelola dalam kode aplikasi. Jika Anda membangun aplikasi baru dari awal, mungkin inilah yang Anda inginkan. Namun, jika Anda sudah memiliki aplikasi berbasis JDO yang ingin dijalankan di App Engine, sebaiknya gunakan konfigurasi persistensi alternatif yang menetapkan nilai properti ini ke 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>

Untuk memahami alasan kegunaannya, ingat bahwa Anda hanya dapat beroperasi pada objek yang termasuk dalam entity group yang sama dalam sebuah transaksi. Aplikasi yang di-build menggunakan database tradisional biasanya mengasumsikan ketersediaan transaksi global, yang memungkinkan Anda memperbarui kumpulan data apa pun di dalam transaksi. Karena datastore App Engine tidak mendukung transaksi global, App Engine akan menampilkan pengecualian jika kode Anda mengasumsikan ketersediaan transaksi global. Daripada memeriksa codebase (yang berpotensi besar) dan menghapus semua kode pengelolaan transaksi, Anda cukup menonaktifkan transaksi datastore. Ini tidak mengatasi asumsi yang dibuat oleh kode Anda tentang atomicity modifikasi multi-data, tetapi memungkinkan Anda membuat aplikasi berfungsi sehingga Anda dapat berfokus untuk memfaktorkan ulang kode transaksional secara bertahap dan sesuai kebutuhan, bukan sekaligus.