Anda dapat membuat model hubungan antara objek persisten menggunakan kolom jenis objek. Hubungan antara objek persisten dapat dijelaskan sebagai dimiliki, saat salah satu objek tidak dapat ada tanpa yang lain, atau tidak dimiliki, saat kedua objek tersebut dapat ada, terlepas dari hubungannya mereka satu sama lain. Implementasi App Engine antarmuka JDO dapat menggunakan model hubungan one-to-one yang dimiliki dan tidak dimiliki serta hubungan one-to-many, baik yang searah maupun dua arah.
Hubungan yang tidak dimiliki tidak didukung dalam plugin DataNucleus versi 1.0 untuk App Engine, tetapi Anda dapat mengelola sendiri hubungan ini dengan menyimpan kunci datastore langsung di kolom. App Engine membuat entity terkait dalam entity group secara otomatis untuk mendukung pembaruan objek terkait secara bersamaan— tetapi aplikasi bertanggung jawab untuk mengetahui kapan harus menggunakan transaksi datastore.
Plugin DataNucleus versi 2.x untuk App Engine mendukung hubungan yang tidak dimiliki dengan sintaksis alami. Bagian Hubungan yang Tidak Dimiliki menjelaskan cara membuat hubungan yang tidak dimiliki di setiap versi plugin. Untuk melakukan upgrade ke plugin DataNucleus untuk App Engine versi 2.x, lihat Bermigrasi ke Plugin DataNucleus untuk App Engine Versi 2.x.
Hubungan One-to-One yang Dimiliki
Anda membuat hubungan one-to-one satu arah yang dimiliki antara dua objek persisten menggunakan kolom yang jenisnya adalah class yang terkait.
Contoh berikut menentukan class data ContactInfo dan class data Karyawan, dengan hubungan one-to-one dari Karyawan ke ContactInfo.
ContactInfo.java
import com.google.appengine.api.datastore.Key; // ... imports ... @PersistenceCapable public class ContactInfo { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private String streetAddress; // ... }
Employee.java
import ContactInfo; // ... imports ... @PersistenceCapable public class Employee { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private ContactInfo contactInfo; ContactInfo getContactInfo() { return contactInfo; } void setContactInfo(ContactInfo contactInfo) { this.contactInfo = contactInfo; } // ... }
Objek persisten direpresentasikan sebagai dua entity yang berbeda di datastore, dengan dua jenis berbeda. Hubungan tersebut direpresentasikan menggunakan hubungan grup entity: kunci turunan menggunakan kunci induk sebagai induk grup entity-nya. Saat aplikasi mengakses objek turunan menggunakan kolom objek induk, implementasi JDO akan melakukan kueri induk entity group untuk mendapatkan turunan.
Class turunan harus memiliki kolom kunci yang jenisnya dapat berisi informasi kunci induk: baik nilai Kunci maupun Kunci yang dienkode sebagai string. Lihat Membuat Data: Kunci untuk informasi tentang jenis kolom kunci.
Anda membuat hubungan dua arah one-to-one menggunakan kolom di kedua
class, dengan anotasi di kolom class turunan untuk mendeklarasikan bahwa
kolom tersebut mewakili hubungan dua arah. Kolom class turunan
harus memiliki anotasi @Persistent
dengan argumen
mappedBy = "..."
, di mana nilainya adalah nama kolom di
class induk. Jika kolom pada satu objek terisi, kolom referensi terkait
pada objek lainnya akan otomatis diisi.
ContactInfo.java
import Employee; // ... @Persistent(mappedBy = "contactInfo") private Employee employee;
Objek turunan dimuat dari datastore saat diakses untuk
pertama kalinya. Jika Anda tidak mengakses objek turunan pada objek induk, entity
untuk objek turunan tidak akan pernah dimuat. Jika ingin memuat turunan, Anda
dapat "menyentuhnya" sebelum menutup PersistenceManager (misalnya dengan
memanggil getContactInfo()
dalam contoh di atas) atau secara eksplisit menambahkan
kolom turunan ke grup pengambilan default sehingga bisa diambil dan dimuat dengan
induk:
Employee.java
import ContactInfo; // ... @Persistent(defaultFetchGroup = "true") private ContactInfo contactInfo;
Hubungan One-to-Many yang Dimiliki
Untuk membuat hubungan one-to-many antara objek dari satu class dengan beberapa objek, gunakan Koleksi class terkait:
Employee.java
import java.util.List; // ... @Persistent private List<ContactInfo> contactInfoSets;
Hubungan dua arah one-to-many mirip dengan hubungan one-to-one, dengan
kolom di class induk menggunakan anotasi
@Persistent(mappedBy = "...")
, di mana nilainya adalah nama kolom
pada class turunan:
Employee.java
import java.util.List; // ... @Persistent(mappedBy = "employee") private List<ContactInfo> contactInfoSets;
ContactInfo.java
import Employee; // ... @Persistent private Employee employee;
Jenis koleksi yang tercantum dalam Menentukan Class Data: Koleksi didukung untuk hubungan one-to-many. Namun, array tidak didukung untuk hubungan one-to-many.
App Engine tidak mendukung kueri gabung: Anda tidak dapat mengkueri parent entity menggunakan atribut entity turunan. (Anda dapat membuat kueri properti class yang tersemat karena class tersemat menyimpan properti pada parent entity. Lihat Menentukan Class Data: Class Tersemat.)
Cara Koleksi yang Diurutkan Mempertahankan Urutannya
Koleksi yang diurutkan, seperti List<...>
, mempertahankan urutan
objek saat objek induk disimpan. JDO mengharuskan database mempertahankan
urutan ini dengan menyimpan posisi setiap objek sebagai properti objek.
App Engine menyimpannya sebagai properti entity terkait, menggunakan
nama properti yang sama dengan nama kolom induk dan diikuti dengan
_INTEGER_IDX
. Properti posisi tidak efisien. Jika
elemen ditambahkan, dihapus, atau dipindahkan dalam koleksi, semua entity setelah
tempat yang dimodifikasi dalam koleksi harus diperbarui. Proses ini dapat berjalan lambat dan
rentan error jika tidak dilakukan dalam transaksi.
Jika tidak perlu mempertahankan urutan arbitrer dalam koleksi, tetapi perlu menggunakan jenis koleksi yang diurutkan, Anda dapat menentukan pengurutan berdasarkan properti elemen menggunakan anotasi, ekstensi untuk JDO yang disediakan oleh DataNucleus:
import java.util.List; import javax.jdo.annotations.Extension; import javax.jdo.annotations.Order; import javax.jdo.annotations.Persistent; // ... @Persistent @Order(extensions = @Extension(vendorName="datanucleus",key="list-ordering", value="state asc, city asc")) private List<ContactInfo> contactInfoSets = new ArrayList<ContactInfo>();
Anotasi @Order
(menggunakan ekstensi list-ordering
)
menentukan urutan elemen koleksi yang diinginkan sebagai
klausa pengurutan JDOQL. Pengurutan menggunakan nilai properti elemen.
Seperti halnya kueri, semua elemen koleksi harus memiliki nilai untuk
properti yang digunakan dalam klausa pengurutan.
Mengakses koleksi akan menjalankan kueri. Jika klausa pengurutan sebuah kolom menggunakan lebih dari satu tata urutan, kueri memerlukan indeks Datastore; lihat halaman Indeks Datastore untuk informasi lebih lanjut.
Agar efisien, selalu gunakan klausa pengurutan eksplisit untuk hubungan one-to-many dari jenis koleksi yang diurutkan, jika memungkinkan.
Hubungan yang Tidak Dimiliki
Selain hubungan yang dimiliki, JDO API juga menyediakan fasilitas untuk mengelola hubungan yang tidak dimiliki. Fasilitas ini berfungsi secara berbeda, bergantung pada versi plugin DataNucleus untuk App Engine yang digunakan:
- Versi 1
plugin DataNucleus tidak menerapkan hubungan yang tidak dimiliki menggunakan sintaksis alami,
tetapi Anda masih dapat mengelola hubungan ini menggunakan nilai
Key
sebagai pengganti instance (atau Koleksi instance) dari objek model Anda. Anda dapat menganggap penyimpanan objek Key sebagai pemodelan "foreign key" arbitrer antara dua objek. Datastore tidak menjamin | integritas referensial dengan referensi Kunci ini, tetapi penggunaan Kunci memudahkan pembuatan model (dan kemudian mengambil) hubungan apa pun antara dua objek.
Namun, jika menggunakan cara ini, Anda harus memastikan kunci tersebut memiliki jenis yang sesuai. JDO dan compiler tidak memeriksa jenisKey
untuk Anda. - Plugin DataNucleus Versi 2.x mengimplementasikan hubungan yang tidak dimiliki menggunakan sintaksis alami.
Tips: Dalam beberapa kasus, Anda mungkin perlu membuat model hubungan yang dimiliki seolah-olah hubungan tersebut tidak dimiliki. Ini karena semua objek yang terlibat dalam hubungan yang dimiliki akan otomatis ditempatkan dalam grup entity yang sama, dan grup entity hanya dapat mendukung satu hingga sepuluh penulisan per detik. Jadi, misalnya, jika objek induk menerima 0,75 penulisan per detik dan objek turunan menerima 0,75 penulisan per detik, mungkin masuk akal untuk memodelkan hubungan ini sebagai tidak dimiliki sehingga keduanya induk dan anak berada dalam kelompok entitas independen mereka sendiri.
Hubungan One-to-One yang Tidak Dimiliki
Misalkan Anda ingin membuat model orang dan makanan, di mana satu orang hanya dapat memiliki satu makanan favorit, sedangkan makanan favorit bukan milik orang itu karena itu bisa menjadi makanan favorit beberapa orang. Bagian ini menunjukkan cara melakukannya.
Di JDO 2.3
Dalam contoh ini, kita memberi Person
anggota dari jenis
Key
, di mana Key
adalah ID unik dari
objek Food
. Jika instance Person
dan instance Food
yang dirujuk oleh
Person.favoriteFood
tidak berada dalam grup entity yang sama, Anda
tidak dapat mengupdate orang tersebut dan makanan favoritnya dalam satu
transaksi, kecuali konfigurasi JDO Anda disetel ke
aktifkan
transaksi lintas grup (XG).
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Key favoriteFood; // ... }
Food.java
import Person; // ... imports ... @PersistenceCapable public class Food { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; // ... }
Di JDO 3.0
Dalam contoh ini, kita membuat anggota pribadi jenis Food
, dan bukan memberikan kunci yang mewakili
makanan favoritnya kepada Person
:
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent @Unowned private Food favoriteFood; // ... }
Food.java
import Person; // ... imports ... @PersistenceCapable public class Food { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; // ... }
Hubungan One-to-Many yang Tidak Dimiliki
Sekarang misalkan kita ingin membiarkan satu orang memiliki beberapa makanan favorit. Sekali lagi, makanan favorit bukan hanya milik orang tersebut karena bisa menjadi makanan favorit banyak orang.
Di JDO 2.3
Dalam contoh ini, alih-alih memberi Orang jenis anggota
Set<Food>
untuk mewakili makanan favorit orang tersebut, kita
memberi Orang jenis anggota Set<Key>
, di mana
himpunan tersebut berisi pengenal unik dari objek Food
. Perhatikan bahwa, jika
sebuah instance Person
dan sebuah instanceFood
dimuat dalamPerson.favoriteFoods
tidak berada dalam entity group yang sama,
Anda harus menyetel konfigurasi JDO ke
aktifkan
transaksi lintas grup (XG) jika Anda ingin memperbaruinya dalam transaksi yang sama.
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Set<Key> favoriteFoods; // ... }
Di JDO 3.0
Dalam contoh ini, kita memberikan Orang jenis Set<Food>
kepada Orang dengan kumpulan yang mewakili makanan favorit Orang tersebut.
Person.java
// ... imports ... @PersistenceCapable public class Person { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Set<Food> favoriteFoods; // ... }
Hubungan Many-to-Many
Kita dapat membuat model hubungan many-to-many dengan mempertahankan koleksi kunci
di kedua sisi hubungan. Mari kita sesuaikan contoh agar
Food
selalu melacak orang yang menganggapnya sebagai favorit:
Person.java
import java.util.Set; import com.google.appengine.api.datastore.Key; // ... @Persistent private Set<Key> favoriteFoods;
Food.java
import java.util.Set; import com.google.appengine.api.datastore.Key; // ... @Persistent private Set<Key> foodFans;
Dalam contoh ini, Person
mempertahankan satu Set nilai Key
yang secara unik mengidentifikasiFood
objek yang disukai,
danFood
mempertahankan satu Set nilai Key
yang secara
unik mengidentifikasi objek Person
yang menganggapnya
sebagai favorit.
Saat membuat model many-to-many menggunakan nilai Key
, perlu diperhatikan bahwa
aplikasi bertanggung jawab untuk mempertahankan kedua sisi hubungan:
Album.java
// ... public void addFavoriteFood(Food food) { favoriteFoods.add(food.getKey()); food.getFoodFans().add(getKey()); } public void removeFavoriteFood(Food food) { favoriteFoods.remove(food.getKey()); food.getFoodFans().remove(getKey()); }
Jika sebuah instance Person
dan instance
Food
yang terdapat dalam Person.favoriteFoods
tidak berada dalam entity group yang sama, dan ingin memperbaruinya dalam satu transaksi, Anda harus menetapkan konfigurasi JDO ke
aktifkan
transaksi lintas grup (XG).
Hubungan, Entity Group, dan Transaksi
Saat aplikasi Anda menyimpan objek dengan hubungan yang dimiliki ke datastore, semua objek lain yang dapat dijangkau melalui hubungan dan perlu disimpan (objek tersebut baru atau telah dimodifikasi sejak terakhir dimuat) disimpan secara otomatis. Hal ini memiliki implikasi penting bagi transaksi dan entity group.
Pertimbangkan contoh berikut menggunakan hubungan searah antara
class Employee
dan ContactInfo
di atas:
Employee e = new Employee(); ContactInfo ci = new ContactInfo(); e.setContactInfo(ci); pm.makePersistent(e);
Saat objek Employee
baru disimpan menggunakan
metode pm.makePersistent()
, objek ContactInfo
terkait
yang baru akan disimpan secara otomatis. Karena kedua objek ini
baru, App Engine akan membuat dua entity baru dalam grup entity yang sama,
menggunakan entity Employee
sebagai induk dari
entity ContactInfo
. Demikian pula, jika objek Employee
telah disimpan dan objek ContactInfo
yang terkait masih baru,
App Engine akan membuat entity ContactInfo
menggunakan entity Employee
yang ada
sebagai induknya.
Namun, perhatikan bahwa panggilan ke pm.makePersistent()
dalam contoh ini
tidak menggunakan transaksi. Tanpa transaksi eksplisit, kedua
entity akan dibuat menggunakan tindakan atomik yang terpisah. Dalam hal ini,
pembuatan entitas Karyawan dapat berhasil, tetapi pembuatan
entitas ContactInfo akan gagal. Untuk memastikan bahwa kedua entity berhasil dibuat
atau tidak ada entity yang dibuat, Anda harus menggunakan transaksi:
Employee e = new Employee(); ContactInfo ci = new ContactInfo(); e.setContactInfo(ci); try { Transaction tx = pm.currentTransaction(); tx.begin(); pm.makePersistent(e); tx.commit(); } finally { if (tx.isActive()) { tx.rollback(); } }
Jika kedua objek disimpan sebelum hubungan terbentuk,
App Engine tidak dapat "memindahkan" entity ContactInfo
yang ada ke dalam
entity group Employee
, karena grup entity hanya dapat
ditetapkan saat entity dibuat. App Engine dapat membangun
hubungan dengan referensi, tetapi entity terkait tidak akan berada dalam grup
yang sama. Dalam hal ini, kedua entity tersebut dapat diperbarui atau dihapus dalam
transaksi yang sama jika Anda menetapkan konfigurasi JDO untuk
mengaktifkan
transaksi lintas grup (XG). Jika Anda tidak menggunakan transaksi XG, upaya
untuk memperbarui atau menghapus entity dari grup yang berbeda dalam transaksi
yang sama
akan memunculkan JDOFatalUserException.
Menyimpan objek induk yang objek turunannya telah diubah akan menyimpan perubahan pada objek turunan. Sebaiknya izinkan objek induk untuk mempertahankan persistensi untuk semua objek turunan terkait dengan cara ini, dan menggunakan transaksi saat menyimpan perubahan.
Turunan yang Bergantung dan Cascading Delete
Hubungan yang dimiliki dapat bersifat "dependensi", yang berarti bahwa turunan tidak dapat ada
tanpa induknya. Jika hubungan bersifat dependen dan objek induk dihapus,
semua objek turunan juga akan dihapus. Memutus hubungan dependen
yang dimiliki dengan menetapkan nilai baru ke kolom dependen pada induk juga
akan menghapus turunan yang lama. Anda dapat mendeklarasikan hubungan one-to-one yang dimiliki sebagai
dependensi dengan menambahkan dependent="true"
ke anotasi Persistent
kolom pada objek induk yang merujuk pada turunan:
// ... @Persistent(dependent = "true") private ContactInfo contactInfo;
Anda dapat mendeklarasikan hubungan one-to-many yang dimiliki sebagai dependensi dengan menambahkan
anotasi @Element(dependent = "true")
ke kolom pada
objek induk yang mengacu pada koleksi turunan:
import javax.jdo.annotations.Element; // ... @Persistent @Element(dependent = "true") private ListcontactInfos;
Seperti halnya membuat dan memperbarui objek, jika ingin agar setiap penghapusan dalam cascading delete terjadi dalam satu tindakan atomik, Anda harus melakukan penghapusan dalam transaksi.
Catatan: Implementasi JDO berfungsi untuk menghapus objek turunan dependen, bukan datastore. Jika Anda menghapus parent entity menggunakan API level rendah atau Konsol Google Cloud, objek turunan yang terkait tidak akan dihapus.
Hubungan Polimorf
Meskipun spesifikasi JDO menyertakan dukungan untuk hubungan
polimorf, hubungan polimorf belum didukung dalam implementasi
App Engine. Batasan ini ingin kami hapus dalam rilis produk
mendatang. Jika Anda perlu merujuk ke beberapa jenis objek melalui class dasar
yang sama, sebaiknya gunakan strategi yang sama dengan yang digunakan untuk menerapkan hubungan
yang tidak dimiliki: menyimpan Referensi kunci. Misalnya, jika Anda memiliki
class dasar Recipe
dengan spesialisasi Appetizer
, Entree
,
dan Dessert
, dan Anda ingin membuat modelRecipe
dari Chef
, Anda dapat membuat modelnya sebagai berikut:
Recipe.java
import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.Inheritance; import javax.jdo.annotations.InheritanceStrategy; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; @PersistenceCapable @Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE) public abstract class Recipe { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private int prepTime; }
Appetizer.java
// ... imports ... @PersistenceCapable public class Appetizer extends Recipe { // ... appetizer-specific fields }
Entree.java
// ... imports ... @PersistenceCapable public class Entree extends Recipe { // ... entree-specific fields }
Dessert.java
// ... imports ... @PersistenceCapable public class Dessert extends Recipe { // ... dessert-specific fields }
Chef.java
// ... imports ... @PersistenceCapable public class Chef { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent(dependent = "true") private Recipe favoriteRecipe; }
Sayangnya, jika membuat instance Entree
dan menetapkannya ke
Chef.favoriteRecipe
, Anda akan mendapatkan
UnsupportedOperationException
saat mencoba mempertahankan
objek Chef
. Hal ini karena jenis runtime objek,
Entree
, tidak cocok dengan kolom jenis hubungan yang dideklarasikan,
Recipe
. Solusinya adalah dengan mengubah jenis
Chef.favoriteRecipe
dari Recipe
menjadi
Key
:
Chef.java
// ... imports ... @PersistenceCapable public class Chef { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private Key favoriteRecipe; }
Karena Chef.favoriteRecipe
tidak lagi menjadi kolom hubungan,
kolom ini dapat merujuk ke objek dari jenis apa pun. Kelemahannya adalah, seperti halnya hubungan yang tidak
dimiliki, Anda perlu mengelolanya secara manual.