Hubungan Entitas di JDO

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 jenis Key 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 List contactInfos;

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.