Java Persistence API (JPA) adalah antarmuka standar untuk mengakses database di Java, yang menyediakan pemetaan otomatis antara class Java dan tabel database. Ada plugin open source yang tersedia untuk menggunakan JPA dengan Datastore, dan halaman ini memberikan informasi tentang cara mulai menggunakannya.
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. JPA 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.
Plugin versi 1.x disertakan dalam App Engine Java SDK, yang mengimplementasikan JPA versi 1.0. Implementasi ini didasarkan pada DataNucleus Access Platform versi 1.1.
Catatan: Petunjuk di halaman ini berlaku untuk JPA versi 1, yang menggunakan plugin DataNucleus versi 1.x untuk App Engine. Plugin DataNucleus versi 2.x juga tersedia, sehingga Anda dapat menggunakan JPA 2.0. Plugin 2.x menyediakan sejumlah API dan fitur baru; tetapi upgrade ini tidak sepenuhnya kompatibel dengan versi 1.x. Jika melakukan build ulang aplikasi menggunakan JPA 2.0, Anda perlu mengupdate dan menguji ulang kode Anda. Untuk mengetahui informasi selengkapnya tentang versi baru, lihat Menggunakan JPA 2.0 dengan App Engine.
Menyiapkan JPA
Untuk menggunakan JPA guna mengakses datastore, aplikasi App Engine memerlukan hal berikut:
- JPA dan JAR datastore harus berada di direktori
war/WEB-INF/lib/
aplikasi. - File konfigurasi bernama
persistence.xml
harus berada di direktoriwar/WEB-INF/classes/META-INF/
aplikasi, dengan konfigurasi yang memberi tahu JPA agar menggunakan datastore App Engine. - Proses build project harus melakukan langkah "peningkatan" pascakompilasi pada class data yang dikompilasi untuk mengaitkannya dengan implementasi JPA.
Menyalin JAR
JPA dan JAR datastore disertakan dengan App Engine Java SDK. Anda dapat
menemukannya di direktori appengine-java-sdk/lib/user/orm/
.
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 DataNucleus App Engine menggunakan JAR ini untuk
mengakses datastore.
Membuat File persistence.xml
Antarmuka JPA memerlukan file konfigurasi bernama
persistence.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" ?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="transactions-optional"> <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider> <properties> <property name="datanucleus.NontransactionalRead" value="true"/> <property name="datanucleus.NontransactionalWrite" value="true"/> <property name="datanucleus.ConnectionURL" value="appengine"/> </properties> </persistence-unit> </persistence>
Kebijakan Baca Datastore dan Batas Waktu Panggilan
Seperti yang dijelaskan di halaman Kueri
Datastore, Anda dapat menetapkan kebijakan baca (konsistensi kuat vs. konsistensi
tertunda) dan batas waktu panggilan datastore untuk
EntityManagerFactory
di file persistence.xml
.
Setelan ini masuk ke elemen <persistence-unit>
. Semua
panggilan yang dilakukan dengan instance EntityManager
tertentu menggunakan
konfigurasi yang dipilih saat pengelola dibuat oleh
EntityManagerFactory
. Anda juga dapat mengganti opsi ini untuk setiap Query
(dijelaskan di bawah).
Untuk menetapkan kebijakan baca, sertakan properti bernama datanucleus.appengine.datastoreReadConsistency
. Kemungkinan nilainya adalah EVENTUAL
(untuk pembacaan dengan konsistensi tertunda) dan STRONG
(untuk pembacaan dengan konsistensi kuat). Jika tidak ditentukan, jumlah defaultnya adalah STRONG
.
<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
Anda dapat menetapkan batas waktu panggilan datastore terpisah untuk pembacaan dan penulisan. Untuk
pembacaan, gunakan properti standar JPA
javax.persistence.query.timeout
. Untuk penulisan, gunakan datanucleus.datastoreWriteTimeout
. Nilainya adalah jumlah waktu,
dalam milidetik.
<property name="javax.persistence.query.timeout" value="5000" /> <property name="datanucleus.datastoreWriteTimeout" 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-unit>
dalam
file persistence.xml
yang sama, dengan menggunakan atribut name
yang berbeda, untuk menggunakan instance EntityManager
dengan konfigurasi
yang berbeda di aplikasi yang sama. Misalnya, file
persistence.xml
berikut membuat dua kumpulan konfigurasi, satu
bernama "transactions-optional"
dan lainnya bernama
"eventual-reads-short-deadlines"
:
<?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="transactions-optional"> <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider> <properties> <property name="datanucleus.NontransactionalRead" value="true"/> <property name="datanucleus.NontransactionalWrite" value="true"/> <property name="datanucleus.ConnectionURL" value="appengine"/> </properties> </persistence-unit> <persistence-unit name="eventual-reads-short-deadlines"> <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider> <properties> <property name="datanucleus.NontransactionalRead" value="true"/> <property name="datanucleus.NontransactionalWrite" value="true"/> <property name="datanucleus.ConnectionURL" value="appengine"/> <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" /> <property name="javax.persistence.query.timeout" value="5000" /> <property name="datanucleus.datastoreWriteTimeout" value="10000" /> </properties> </persistence-unit> </persistence>
Lihat Mendapatkan Instance
EntityManager di bawah untuk informasi tentang cara membuat EntityManager
dengan set konfigurasi bernama.
Anda dapat mengganti kebijakan baca dan memanggil batas waktu untuk setiap
objek Query
. Guna mengganti kebijakan baca untuk Query
,
panggil metode setHint()
sebagai berikut:
Query q = em.createQuery("select from " + Book.class.getName()); q.setHint("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");
Seperti di atas, kemungkinan nilainya adalah "EVENTUAL"
dan
"STRONG"
.
Untuk mengganti waktu tunggu baca, panggil setHint()
sebagai berikut:
q.setHint("javax.persistence.query.timeout", 3000);
Tidak ada cara lain untuk mengganti konfigurasi opsi ini saat Anda mengambil entity berdasarkan kunci.
Meningkatkan Class Data
Implementasi DataNucleus JPA menggunakan langkah "peningkatan" pascakompilasi dalam proses build untuk mengaitkan class data dengan implementasi JPA.
Anda dapat melakukan langkah peningkatan pada class yang dikompilasi dari command line dengan perintah berikut:
java -cp classpath org.datanucleus.enhancer.DataNucleusEnhancer class-files
Classpath harus berisi JAR
datanucleus-core-*.jar
, datanucleus-jpa-*
,
datanucleus-enhancer-*.jar
, asm-*.jar
, dan
geronimo-jpa-*.jar
(dengan *
adalah nomor versi
yang sesuai untuk setiap JAR) dari direktori appengine-java-sdk/lib/tools/
, serta semua class data Anda.
Untuk mengetahui informasi selengkapnya tentang enhancer bytecode DataNucleus, lihat dokumentasi DataNucleus.
Mendapatkan Instance EntityManager
Aplikasi berinteraksi dengan JPA menggunakan instance class
EntityManager
. Anda mendapatkan instance ini dengan membuat instance dan memanggil metode pada
instance class EntityManagerFactory
. Factory menggunakan
konfigurasi JPA (yang diidentifikasi dengan nama "transactions-optional"
)
untuk membuat instance EntityManager
.
Karena instance EntityManagerFactory
memerlukan waktu untuk
diinisialisasi, sebaiknya gunakan kembali satu instance sebanyak mungkin. Cara
mudah untuk melakukannya adalah dengan membuat class wrapper singleton dengan instance
statis, seperti berikut:
EMF.java
import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public final class EMF { private static final EntityManagerFactory emfInstance = Persistence.createEntityManagerFactory("transactions-optional"); private EMF() {} public static EntityManagerFactory get() { return emfInstance; } }
Tips: "transactions-optional"
merujuk pada
nama konfigurasi yang ditetapkan dalam file persistence.xml
. Jika aplikasi
Anda menggunakan beberapa set konfigurasi, Anda harus memperluas kode ini untuk memanggil
Persistence.createEntityManagerFactory()
seperti yang diinginkan. Kode Anda
harus menyimpan instance singleton setiap EntityManagerFactory
dalam cache.
Aplikasi menggunakan instance factory untuk membuat satu instance EntityManager
untuk setiap permintaan yang mengakses datastore.
import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import EMF; // ... EntityManager em = EMF.get().createEntityManager();
Anda menggunakan EntityManager
untuk menyimpan, memperbarui, dan menghapus objek
data, serta menjalankan kueri datastore.
Setelah selesai dengan instance EntityManager
, Anda harus memanggil
metode close()
-nya. Menggunakan instance EntityManager
setelah memanggil metode close()
merupakan error.
try { // ... do stuff with em ... } finally { em.close(); }
Anotasi Class dan Kolom
Setiap objek yang disimpan oleh JPA akan menjadi entity di datastore App Engine. Jenis entity berasal dari nama sederhana class (tanpa nama paket). Setiap kolom persisten dari class ini mewakili properti entity, menggunakan nama properti yang sama dengan nama kolom (kapitalisasinya tetap).
Untuk mendeklarasikan class Java sebagai dapat disimpan dan diambil dari
datastore dengan JPA, berikan anotasi @Entity
pada class tersebut. Misalnya:
import javax.persistence.Entity; @Entity public class Employee { // ... }
Kolom class data yang akan disimpan di datastore harus
secara default berjenis persisten atau dinyatakan secara eksplisit sebagai persisten.
Anda dapat menemukan diagram yang menjelaskan perilaku persistensi default JPA di
situs DataNucleus. Untuk mendeklarasikan kolom sebagai persisten, berikan
anotasi @Basic
:
import java.util.Date; import javax.persistence.Enumerated; import com.google.appengine.api.datastore.ShortBlob; // ... @Basic private ShortBlob data;
Jenis kolom dapat berupa salah satu dari berikut:
- salah satu jenis inti yang didukung oleh datastore
- Koleksi (seperti
java.util.List<...>
) nilai jenis datastore inti - instance atau Koleksi instance dari class
@Entity
- class yang disematkan, disimpan sebagai properti pada entity
Class data harus memiliki konstruktor default publik atau yang dilindungi, dan satu
kolom yang dikhususkan untuk menyimpan kunci utama entity datastore
yang sesuai. Anda dapat memilih antara empat jenis kolom kunci yang berbeda, masing-masing menggunakan
jenis nilai dan anotasi yang berbeda. (Lihat
Membuat
Data: Kunci untuk informasi selengkapnya.) Kolom kunci yang paling sederhana adalah nilai bilangan bulat panjang
yang otomatis diisi oleh JPA dengan nilai unik di semua
instance class lainnya saat objek disimpan ke datastore untuk
pertama kalinya. Kunci bilangan bulat panjang menggunakan anotasi @Id
, dan
anotasi @GeneratedValue(strategy = GenerationType.IDENTITY)
:
import com.google.appengine.api.datastore.Key; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; // ... @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key;
Berikut adalah contoh class data:
import com.google.appengine.api.datastore.Key; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key; private String firstName; private String lastName; private Date hireDate; // Accessors for the fields. JPA doesn't use these, but your application does. public Key getKey() { return 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; } }
Pewarisan
JPA mendukung pembuatan class data yang menggunakan pewarisan. Sebelum membahas cara kerja pewarisan JPA di App Engine, sebaiknya baca dokumentasi DataNucleus tentang subjek ini, lalu kembali lagi. Sudah? Oke. Pewarisan JPA di App Engine berfungsi seperti yang dijelaskan dalam dokumentasi DataNucleus dengan beberapa pembatasan tambahan. Kita akan membahas batasan ini dan memberikan beberapa contoh konkret.
Strategi pewarisan "JOINED" memungkinkan Anda membagi data untuk satu objek data di beberapa "tabel", tetapi karena App Engine datastore tidak mendukung penggabungan, operasi pada objek data dengan strategi pewarisan ini memerlukan remote procedure call untuk setiap tingkat pewarisan. Hal ini berpotensi sangat tidak efisien, sehingga strategi pewarisan "JOINED" tidak didukung pada class data.
Kedua, strategi pewarisan "SINGLE_TABLE" memungkinkan Anda menyimpan data untuk objek data dalam satu "tabel" yang terkait dengan class persisten di root hierarki pewarisan Anda. Meskipun tidak ditemukan inefisiensi dalam strategi ini, strategi tersebut saat ini tidak didukung. Kami dapat memeriksanya kembali dalam rilis mendatang.
Kabar baiknya: Strategi "TABLE_PER_CLASS" dan "MAPPED_SUPERCLASS" berfungsi seperti yang dijelaskan dalam dokumentasi DataNucleus. Perhatikan contohnya:
Worker.java
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @Entity @MappedSuperclass public abstract class Worker { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key; private String department; }
Employee.java
// ... imports ... @Entity public class Employee extends Worker { private int salary; }
Intern.java
import java.util.Date; // ... imports ... @Entity public class Intern extends Worker { private Date internshipEndDate; }
Dalam contoh ini, kami telah menambahkan anotasi @MappedSuperclass
ke
deklarasi class Worker
. Kode ini memberi tahu JPA untuk menyimpan semua
kolom persisten Worker
dalam entity datastore
subclass-nya. Entity datastore yang dibuat sebagai hasil pemanggilan
persist()
dengan instance Employee
akan memiliki dua
properti bernama "department" dan "salary". Entity datastore yang dibuat sebagai hasil pemanggilan persist()
dengan instance Intern
akan memiliki dua properti bernama "department" dan "inernshipEndDate". Tidak akan
ada entity jenis "Worker" di datastore.
Sekarang mari kita buat sedikit lebih menarik. Misalkan, selain memiliki Employee
dan Intern
, kita juga menginginkan spesialisasi Employee
yang mendeskripsikan karyawan yang telah keluar dari perusahaan:
FormerEmployee.java
import java.util.Date; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; // ... imports ... @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class FormerEmployee extends Employee { private Date lastDay; }
Dalam contoh ini, kami telah menambahkan anotasi @Inheritance
ke
deklarasi class FormerEmployee
dengan atribut strategy
yang ditetapkan ke InheritanceType.TABLE_PER_CLASS
. Kode ini memberi tahu JPA untuk
menyimpan semua kolom persisten FormerEmployee
dan
superclass-nya di entity datastore yang terkait dengan instance
FormerEmployee
. Entity datastore yang dibuat sebagai hasil pemanggilan
persist()
dengan instance FormerEmployee
akan memiliki
tiga properti yang bernama "department", "salary", dan "lastDay". Tidak akan pernah ada
entity jenis "Employee" yang sesuai dengan
FormerEmployee
, tetapi jika Anda memanggil persist()
dengan
objek yang jenis runtime-nya adalah Employee
, Anda akan menciptakan entitas
jenis "Employee.
Mengombinasikan hubungan dengan pewarisan dapat dilakukan asalkan jenis kolom hubungan yang dideklarasikan cocok dengan jenis runtime objek yang Anda tetapkan ke kolom tersebut. Lihat bagian tentang Hubungan Polimorf untuk informasi selengkapnya. Bagian ini berisi contoh JDO, tetapi konsep dan batasannya sama untuk JPA.
Fitur JPA 1.0 yang Tidak Didukung
Fitur antarmuka JPA berikut tidak didukung oleh implementasi App Engine:
- Memiliki hubungan many-to-many, dan hubungan yang tidak dimiliki. Anda dapat menerapkan hubungan yang tidak dimiliki menggunakan Nilai kunci eksplisit, meskipun pemeriksaan jenis tidak diterapkan di API.
- Kueri "Gabung". Anda tidak dapat menggunakan kolom entity turunan di filter saat menjalankan kueri pada jenis induk. Perhatikan, Anda dapat menguji kolom hubungan induk secara langsung dalam kueri menggunakan kunci.
- Kueri agregasi (dikelompokkan dengan memiliki, sum, avg, max, min)
- Kueri polimorf. Anda tidak dapat menjalankan kueri class untuk mendapatkan instance subclass. Setiap class diwakili oleh jenis entity terpisah di datastore.