Menentukan Class Data dengan JDO

Anda dapat menggunakan JDO untuk menyimpan objek data Java biasa (terkadang disebut sebagai "Plain Old Java Objects" atau "POJO") di datastore. Setiap objek yang dibuat persisten dengan PersistenceManager akan menjadi entity di datastore. Anda menggunakan anotasi untuk memberi tahu JDO cara menyimpan dan membuat ulang instance class data Anda.

Catatan: Versi JDO sebelumnya menggunakan file XML .jdo, bukan anotasi Java. Ini masih berfungsi dengan JDO 2.3. Dokumentasi ini hanya membahas penggunaan anotasi Java dengan class data.

Anotasi Class dan Kolom

Setiap objek yang disimpan oleh JDO akan menjadi entity di App Engine Datastore. Jenis entity berasal dari nama class sederhana (class dalam menggunakan jalur $ tanpa nama paket). Setiap kolom persisten pada class mewakili properti entity, dengan nama properti yang sama dengan nama kolom (dengan mempertahankan kapitalisasi).

Untuk mendeklarasikan class Java sebagai dapat disimpan dan diambil dari datastore dengan JDO, berikan anotasi @PersistenceCapable pada class tersebut. Contoh:

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Employee {
    // ...
}

Kolom class data yang akan disimpan di datastore harus dideklarasikan sebagai kolom persisten. Untuk mendeklarasikan kolom sebagai persisten, berikan anotasi @Persistent:

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

Untuk mendeklarasikan kolom sebagai tidak persisten (tidak disimpan di datastore, dan tidak dipulihkan saat objek diambil), beri anotasi @NotPersistent.

Tips: JDO menentukan bahwa kolom dari jenis tertentu bersifat persisten secara default jika anotasi @Persistent atau @NotPersistent tidak ditentukan, dan kolom dari semua jenis lainnya tidak persisten secara default. Lihat dokumentasi DataNucleus untuk mengetahui deskripsi lengkap tentang perilaku ini. Karena tidak semua jenis nilai inti App Engine Datastore bersifat persisten secara default sesuai dengan spesifikasi JDO, sebaiknya menganotasikan kolom secara eksplisit sebagai @Persistent atau @NotPersistent untuk memperjelasnya.

Jenis kolom bisa berupa salah satu dari berikut ini. Hal ini dijelaskan secara mendetail di bawah.

  • salah satu jenis inti yang didukung oleh datastore
  • Koleksi (seperti java.util.List<...>) atau array nilai dari jenis datastore inti
  • instance atau Kumpulan instance class @PersistenceCapable
  • instance atau Kumpulan instance class Serialisabel
  • class tersemat yang disimpan sebagai properti pada entity

Class data harus memiliki satu dan hanya satu kolom yang dikhususkan untuk menyimpan kunci utama entity datastore yang sesuai. Anda dapat memilih di antara empat jenis kolom kunci yang berbeda, masing-masing menggunakan jenis nilai dan anotasi yang berbeda. (Lihat Membuat Data: Kunci untuk informasi selengkapnya.) Jenis kolom kunci yang paling fleksibel adalah objek Key yang secara otomatis diisi oleh JDO dengan nilai unik di semua instance class lainnya saat objek disimpan ke datastore untuk pertama kalinya. Kunci utama jenis Key memerlukan anotasi @PrimaryKey dan anotasi @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY):

Tips: Buat semua kolom persisten private atau protected (atau paket dilindungi), dan hanya berikan akses publik melalui metode pengakses. Akses langsung ke kolom persisten dari class lain dapat mengabaikan peningkatan class JDO. Atau, Anda dapat membuat class lain menjadi @PersistenceAware. Lihat dokumentasi DataNucleus untuk mengetahui informasi selengkapnya.

import com.google.appengine.api.datastore.Key;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

Berikut adalah contoh class data:

import com.google.appengine.api.datastore.Key;

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String firstName;

    @Persistent
    private String lastName;

    @Persistent
    private Date hireDate;

    public Employee(String firstName, String lastName, Date hireDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.hireDate = hireDate;
    }

    // Accessors for the fields. JDO 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;
    }
}

Jenis Nilai Inti

Untuk merepresentasikan properti yang berisi satu nilai dari jenis inti, deklarasikan kolom jenis Java, dan gunakan anotasi @Persistent:

import java.util.Date;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    private Date hireDate;

Objek yang Dapat Diserialisasi

Nilai kolom dapat berisi instance class yang dapat diserialisasi, yang menyimpan nilai serial dari instance dalam satu nilai properti jenis Blob. Untuk memberi tahu JDO agar melakukan serialisasi nilai, kolom akan menggunakan anotasi @Persistent(serialized=true). Nilai Blob tidak diindeks dan tidak dapat digunakan dalam filter kueri atau tata urutan.

Berikut adalah contoh class sederhana yang dapat diserialisasi yang mewakili file, termasuk konten file, nama file, dan jenis MIME. Ini bukan class data JDO sehingga tidak ada anotasi persistensi.

import java.io.Serializable;

public class DownloadableFile implements Serializable {
    private byte[] content;
    private String filename;
    private String mimeType;

    // ... accessors ...
}

Untuk menyimpan instance class yang dapat diserialisasi sebagai nilai Blob di properti, deklarasikan kolom yang jenisnya adalah class, lalu gunakan anotasi @Persistent(serialized = "true"):

import javax.jdo.annotations.Persistent;
import DownloadableFile;

// ...
    @Persistent(serialized = "true")
    private DownloadableFile file;

Objek Turunan dan Hubungan

Nilai kolom yang merupakan instance class @PersistenceCapable akan membuat hubungan one-to-one yang dimiliki antara dua objek. Kolom yang merupakan kumpulan referensi semacam itu akan membuat hubungan one-to-many yang dimiliki.

Penting: Hubungan yang dimiliki memiliki implikasi untuk transaksi, entity group, dan cascading delete. Lihat Transaksi dan Hubungan untuk mengetahui informasi selengkapnya.

Berikut adalah contoh sederhana hubungan one-to-one yang dimiliki antara objek Employee dan objek 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;

    @Persistent
    private String city;

    @Persistent
    private String stateOrProvince;

    @Persistent
    private String zipCode;

    // ... accessors ...
}

Employee.java

import ContactInfo;
// ... imports ...

@PersistenceCapable
public class Employee {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private ContactInfo myContactInfo;

    // ... accessors ...
}

Dalam contoh ini, jika aplikasi membuat instance Employee, mengisi kolom myContactInfo dengan instance ContactInfo baru, lalu menyimpan instance Employee dengan pm.makePersistent(...), datastore akan membuat dua entity. Salah satunya adalah jenis "ContactInfo", yang mewakili instance ContactInfo. Jenis lainnya adalah jenis "Employee". Kunci entity ContactInfo memiliki kunci entity Employee sebagai induk entity group-nya.

Kelas yang Disematkan

Class tersemat memungkinkan Anda membuat model nilai kolom menggunakan class tanpa membuat entity datastore baru dan membentuk hubungan. Kolom nilai objek disimpan langsung dalam entity datastore untuk objek yang memuatnya.

Setiap class data @PersistenceCapable dapat digunakan sebagai objek tersemat di class data lain. Kolom @Persistent class disematkan dalam objek. Jika Anda memberikan class untuk menyematkan anotasi @EmbeddedOnly, class tersebut hanya dapat digunakan sebagai class yang disematkan. Class yang disematkan tidak memerlukan kolom kunci utama karena tidak disimpan sebagai entity terpisah.

Berikut adalah contoh class yang disematkan. Contoh ini membuat class yang disematkan menjadi class dalam dari class data yang menggunakannya; ini berguna, tetapi tidak diperlukan untuk membuat kelas yang dapat disematkan.

import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
// ... imports ...

@PersistenceCapable
public class EmployeeContacts {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    Key key;
    @PersistenceCapable
    @EmbeddedOnly
    public static class ContactInfo {
        @Persistent
        private String streetAddress;

        @Persistent
        private String city;

        @Persistent
        private String stateOrProvince;

        @Persistent
        private String zipCode;

        // ... accessors ...
    }

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;
}

Kolom dari class tersemat disimpan sebagai properti pada entity, menggunakan nama setiap kolom dan nama properti yang sesuai. Jika Anda memiliki lebih dari satu kolom pada objek yang jenisnya adalah class tersemat, Anda harus mengganti nama salah satu kolom agar tidak bertentangan dengan kolom lainnya. Anda menentukan nama kolom baru menggunakan argumen untuk anotasi @Embedded. Contoh:

    @Persistent
    @Embedded
    private ContactInfo homeContactInfo;

    @Persistent
    @Embedded(members = {
        @Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),
        @Persistent(name="city", columns=@Column(name="workCity")),
        @Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),
        @Persistent(name="zipCode", columns=@Column(name="workZipCode")),
    })
    private ContactInfo workContactInfo;

Demikian pula, kolom pada objek tidak boleh menggunakan nama yang bertabrakan dengan kolom class tersemat, kecuali jika kolom tersemat diganti namanya.

Karena properti persisten class tersemat disimpan di entity yang sama seperti kolom lainnya, Anda dapat menggunakan kolom persisten dari class tersemat dalam filter kueri dan tata urutan JDOQL. Anda dapat merujuk ke kolom tersemat menggunakan nama kolom luar, titik (.), dan nama kolom tersemat. Ini akan berfungsi terlepas dari apakah nama properti untuk kolom tersemat telah diubah menggunakan anotasi @Column atau tidak.

    select from EmployeeContacts where workContactInfo.zipCode == "98105"

Koleksi

Properti datastore dapat memiliki lebih dari satu nilai. Di JDO, koleksi ini diwakili oleh satu kolom dengan jenis Koleksi, dengan koleksi merupakan salah satu jenis nilai inti atau class Serializable. Jenis Koleksi berikut didukung:

  • java.util.ArrayList<...>
  • java.util.HashSet<...>
  • java.util.LinkedHashSet<...>
  • java.util.LinkedList<...>
  • java.util.List<...>
  • java.util.Map<...>
  • java.util.Set<...>
  • java.util.SortedSet<...>
  • java.util.Stack<...>
  • java.util.TreeSet<...>
  • java.util.Vector<...>

Jika kolom dideklarasikan sebagai List, objek yang ditampilkan oleh datastore memiliki nilai ArrayList. Jika kolom dideklarasikan sebagai Set, datastore akan menampilkan HashSet. Jika kolom dideklarasikan sebagai SortedSet, datastore akan menampilkan TreeSet.

Misalnya, kolom yang jenisnya List<String> disimpan sebagai nol atau beberapa nilai string untuk properti, satu untuk setiap nilai dalam List.

import java.util.List;
// ... imports ...

// ...
    @Persistent
    List<String> favoriteFoods;

Koleksi objek turunan (dari class @PersistenceCapable) membuat beberapa entity dengan hubungan one-to-many. Lihat Hubungan.

Properti Datastore dengan lebih dari satu nilai memiliki perilaku khusus untuk filter kueri dan tata urutan. Lihat halaman Kueri Datastore untuk informasi selengkapnya.

Kolom Objek dan Properti Entity

Datastore App Engine membedakan antara entity tanpa properti tertentu dan entity yang memiliki nilai null untuk properti. JDO tidak mendukung perbedaan ini: setiap kolom objek memiliki nilai, mungkin null. Jika kolom dengan jenis nilai nullable (sesuatu selain jenis bawaan seperti int atau boolean) ditetapkan ke null, saat objek disimpan, entity yang dihasilkan akan memiliki properti yang telah ditetapkan dengan nilai null.

Jika entity datastore dimuat ke dalam objek dan tidak memiliki properti untuk salah satu kolom objek dan jenis kolomnya adalah jenis nilai tunggal nullable, kolom tersebut akan disetel ke null. Saat objek disimpan kembali ke datastore, properti null akan menjadi disetel di datastore ke nilai null. Jika kolom bukan dari jenis nilai nullable, memuat entity tanpa properti yang sesuai akan menampilkan pengecualian. Hal ini tidak akan terjadi jika entity dibuat dari class JDO yang sama dengan yang digunakan untuk membuat ulang instance, tetapi dapat terjadi jika class JDO berubah, atau jika entity dibuat menggunakan API level rendah, bukan JDO.

Jika jenis kolom adalah Koleksi jenis data inti atau class Serializable dan tidak ada nilai untuk properti pada entity, koleksi kosong akan direpresentasikan di datastore dengan menetapkan properti ke satu nilai null. Jika jenis kolom adalah jenis array, kolom ini diberi array berisi 0 elemen. Jika objek dimuat dan tidak ada nilai untuk properti, kolom ini diberi koleksi kosong dari jenis yang sesuai. Secara internal, datastore mengetahui perbedaan antara koleksi kosong dan koleksi yang berisi satu nilai null.

Jika entity memiliki properti tanpa kolom yang sesuai dalam objek, properti tersebut tidak dapat diakses dari objek. Jika objek disimpan kembali ke datastore, properti tambahan akan dihapus.

Jika entity memiliki properti yang nilainya berbeda dengan kolom yang sesuai di objek, JDO akan mencoba mentransmisikan nilai ke jenis kolom tersebut. Jika nilainya tidak dapat ditransmisikan ke jenis kolom, JDO akan menampilkan ClassCastException. Dalam kasus angka (bilangan bulat panjang dan float lebar ganda), nilainya akan dikonversi, bukan ditransmisikan. Jika nilai properti numerik lebih besar dari jenis kolom, konversi akan ditampilkan tanpa menampilkan pengecualian.

Anda dapat mendeklarasikan properti yang tidak diindeks dengan menambahkan baris

    @Extension(vendorName="datanucleus", key="gae.unindexed", value="true")

di atas properti dalam definisi class. Lihat bagian Properti yang Tidak Diindeks pada dokumen utama untuk mengetahui informasi tambahan tentang apa yang dimaksud dengan properti yang tidak diindeks.

Pewarisan

Membuat class data yang memanfaatkan pewarisan adalah hal yang alami untuk dilakukan, dan JDO mendukung hal ini. Sebelum membahas cara kerja pewarisan JDO di App Engine, sebaiknya baca dokumentasi DataNucleus tentang subjek ini, lalu kembali lagi. Selesai? Oke. Pewarisan JDO di App Engine berfungsi seperti yang dijelaskan dalam dokumentasi DataNucleus dengan beberapa batasan tambahan. Kita akan membahas pembatasan ini dan kemudian memberikan beberapa contoh konkret.

Strategi pewarisan "tabel baru" memungkinkan Anda membagi data untuk satu objek data ke beberapa "tabel", tetapi karena datastore App Engine tidak mendukung penggabungan, operasi pada objek data dengan strategi pewarisan ini memerlukan panggilan prosedur jarak jauh untuk setiap tingkat pewarisan. Hal ini berpotensi sangat tidak efisien, sehingga strategi pewarisan "tabel baru" tidak didukung di class data yang tidak berada di root hierarki pewarisannya.

Kedua, strategi pewarisan "superclass-table" memungkinkan Anda menyimpan data untuk objek data di "tabel" superclass-nya. Meskipun strategi ini tidak selalu menyebabkan inefisiensi, saat ini strategi tersebut tidak didukung. Kami mungkin akan meninjaunya kembali dalam rilis mendatang.

Kabar baiknya: Strategi "subclass-table" dan "complete-table" berfungsi seperti yang dijelaskan dalam dokumentasi DataNucleus, dan Anda juga dapat menggunakan "new-table" untuk setiap objek data yang berada di root hierarki pewarisannya. Perhatikan contohnya:

Worker.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 Worker {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String department;
}

Employee.java

// ... imports ...

@PersistenceCapable
public class Employee extends Worker {
    @Persistent
    private int salary;
}

Intern.java

import java.util.Date;
// ... imports ...

@PersistenceCapable
public class Intern extends Worker {
    @Persistent
    private Date internshipEndDate;
}

Dalam contoh ini, kami telah menambahkan anotasi @Inheritance ke deklarasi class Worker dengan atribut strategy> yang disetel ke InheritanceStrategy.SUBCLASS_TABLE. Kode ini memberi tahu JDO untuk menyimpan semua kolom persisten Worker dalam entity datastore dari subclass-nya. Entity datastore yang dibuat sebagai hasil pemanggilan makePersistent() dengan instance Employee memiliki dua properti bernama "departemen" dan "gaji". Entity datastore yang dibuat sebagai hasil pemanggilan makePersistent() dengan instance Intern akan memiliki dua properti yang bernama "departemen" dan "TanggalBerakhirmagang". Datastore tidak berisi entity jenis "Pekerja".

Sekarang mari kita buat semuanya sedikit lebih menarik. Misalnya, selain memiliki Employee dan Intern, kita juga menginginkan spesialisasi Employee yang mendeskripsikan karyawan yang telah keluar dari perusahaan:

FormerEmployee.java

import java.util.Date;
// ... imports ...

@PersistenceCapable
@Inheritance(customStrategy = "complete-table")
public class FormerEmployee extends Employee {
    @Persistent
    private Date lastDay;
}

Dalam contoh ini, kami telah menambahkan anotasi @Inheritance ke deklarasi class FormerEmployee dengan atribut custom-strategy> yang disetel ke "complete-table". Kode ini memberi tahu JDO untuk menyimpan semua kolom persisten FormerEmployee dan superclass-nya di entity datastore yang terkait dengan instance FormerEmployee. Entity datastore yang dibuat sebagai hasil pemanggilan makePersistent() dengan instance FormerEmployee akan memiliki tiga properti yang bernama "departemen", "gaji", dan "Hariterakhir". Tidak ada entitas jenis "Karyawan" yang sesuai dengan FormerEmployee. Namun, jika memanggil makePersistent() dengan objek yang jenis runtime-nya adalah Employee, Anda akan membuat entity jenis "Karyawan".

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 Hubungan Polimorf untuk mengetahui informasi selengkapnya.