Entity, Properti, dan Kunci

Objek data di Datastore dikenal sebagai entity. Entity memiliki satu atau beberapa properti yang telah diberi nama, yang masing-masing dapat memiliki satu atau beberapa nilai. Entity dari jenis yang sama tidak harus memiliki properti yang sama, dan nilai suatu entity untuk properti tertentu tidak semuanya harus memiliki jenis data yang sama. (Jika perlu, aplikasi dapat membuat dan menerapkan pembatasan tersebut dalam model datanya sendiri.)

Datastore mendukung berbagai jenis data untuk nilai properti. Hal ini mencakup, antara lain:

  • Bilangan bulat
  • Angka floating-point
  • String
  • Tanggal
  • Data biner

Untuk mengetahui daftar lengkap jenis, lihat Jenis nilai dan properti.

Setiap entity dalam Datastore memiliki kunci yang secara unik mengidentifikasinya. Kunci ini terdiri dari komponen berikut:

  • Namespace entity, yang memungkinkan multitenancy
  • Jenis entity, yang mengategorikannya untuk tujuan kueri Datastore
  • ID untuk masing-masing entity, yang dapat berupa
    • string nama kunci
    • ID numerik bilangan bulat
  • Jalur ancestor opsional yang mencari entity dalam hierarki Datastore

Aplikasi dapat mengambil setiap entity dari Datastore menggunakan kunci entity, atau mengambil satu atau beberapa entity dengan mengeluarkan kueri berdasarkan kunci entity atau nilai properti.

Java App Engine SDK menyertakan API sederhana, yang disediakan dalam paket com.google.appengine.api.datastore, yang mendukung fitur Datastore secara langsung. Semua contoh dalam dokumen ini didasarkan pada API tingkat rendah ini; Anda dapat memilih untuk menggunakannya langsung di aplikasi Anda atau sebagai dasar untuk membangun lapisan pengelolaan data Anda sendiri.

Datastore sendiri tidak menerapkan batasan apa pun pada struktur entity, seperti apakah properti tertentu memiliki nilai dari jenis tertentu; hal ini bebas ditentukan oleh aplikasi.

Jenis dan ID

Setiap entity Datastore memiliki jenis tertentu, yang mengategorikan entity untuk tujuan kueri: misalnya, aplikasi sumber daya manusia mungkin mewakili setiap karyawan di sebuah perusahaan dengan entitas jenis Employee. Di Datastore API Java, Anda menentukan jenis entity saat membuatnya, sebagai argumen untuk konstruktor Entity(). Semua jenis nama yang dimulai dengan dua garis bawah (__) dicadangkan dan tidak boleh digunakan.

Contoh berikut membuat entity jenis Employee, mengisi nilai propertinya, dan menyimpannya ke Datastore:

Entity employee = new Entity("Employee", "asalieri");
employee.setProperty("firstName", "Antonio");
employee.setProperty("lastName", "Salieri");
employee.setProperty("hireDate", new Date());
employee.setProperty("attendedHrTraining", true);

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(employee);

Selain jenis, setiap entity memiliki ID, yang ditetapkan saat entity dibuat. Karena ID tersebut merupakan bagian dari kunci entity, ID-nya dikaitkan secara permanen dengan entity dan tidak dapat diubah. ID tersebut dapat ditetapkan dengan salah satu dari dua cara berikut:

  • Aplikasi Anda dapat menentukan string key name-nya sendiri untuk entity.
  • Anda dapat meminta Datastore menetapkan ID numerik bilangan bulat ke entity secara otomatis.

Untuk menetapkan nama kunci pada entity, berikan nama tersebut sebagai argumen kedua ke konstruktor saat Anda membuat entity:

Entity employee = new Entity("Employee", "asalieri");

Agar Datastore menetapkan ID numerik secara otomatis, hapus argumen ini:

Entity employee = new Entity("Employee");

Menetapkan ID

Datastore dapat dikonfigurasi untuk menghasilkan ID otomatis menggunakan dua kebijakan ID otomatis yang berbeda:

  • Kebijakan default menghasilkan urutan acak dari ID yang tidak digunakan yang kira-kira didistribusikan secara seragam. Setiap ID dapat memiliki maksimal 16 digit desimal.
  • Kebijakan legacy membuat urutan ID bilangan bulat yang lebih kecil dan tidak berurutan.

Jika Anda ingin menampilkan ID entity kepada pengguna, dan/atau bergantung pada urutannya, sebaiknya Anda menggunakan alokasi manual.

Datastore menghasilkan urutan acak dari ID yang tidak digunakan yang kira-kira didistribusikan secara seragam. Setiap ID dapat memiliki maksimal 16 digit desimal.

Jalur ancestor

Entity dalam Cloud Datastore membentuk ruang yang terstruktur secara hierarkis, yang menyerupai struktur direktori sistem file. Saat membuat entity, Anda dapat memilih untuk menetapkan entity lain sebagai parent;; entity barunya adalah parent; dari parent entity tersebut (perlu diperhatikan bahwa tidak seperti dalam sistem file, parent entity tidak harus benar-benar ada). Entity tanpa parent adalah root entity. Kaitan entity dan induknya bersifat permanen, dan tidak dapat diubah setelah entity tersebut dibuat. Cloud Datastore tidak akan pernah menetapkan ID numerik yang sama ke dua entity dengan parent yang sama, atau ke dua root entity (yang tidak memiliki parent).

Induk, induk dari induk, dan seterusnya dari suatu entity secara rekursif adalah ancestor-nya; turunannya, turunan dari turunannya, dan seterusnya adalah turunannya. Root entity dan semua turunannya termasuk dalam entity group yang sama. Urutan entity yang dimulai dengan root entity dan berlanjut dari parent ke turunan, yang mengarah ke entity tertentu, merupakan jalur ancestor entity tersebut. Kunci lengkap yang mengidentifikasi entity terdiri dari urutan pasangan jenis-ID yang menentukan jalur ancestor-nya dan berakhir dengan entity itu sendiri:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Untuk root entity, jalur ancestor kosong dan kuncinya hanya terdiri dari jenis dan ID entity itu sendiri:

[Person:GreatGrandpa]

Konsep ini diilustrasikan oleh diagram berikut:

Menampilkan hubungan root entity dengan entity
  turunan dalam entity group

Untuk menetapkan induk entity, berikan kunci entity induk sebagai argumen ke konstruktor Entity() saat membuat entity turunan. Anda bisa mendapatkan kunci dengan memanggil metode getKey() entity induk:

Entity employee = new Entity("Employee");
datastore.put(employee);

Entity address = new Entity("Address", employee.getKey());
datastore.put(address);

Jika entity baru juga memiliki nama kunci, berikan nama kunci sebagai argumen kedua ke konstruktor Entity() dan kunci parent entity sebagai argumen ketiga:

Entity address = new Entity("Address", "addr1", employee.getKey());

Transaksi dan entity group

Setiap upaya untuk membuat, memperbarui, atau menghapus entity terjadi dalam konteks transaksi. Satu transaksi dapat mencakup sejumlah operasi tersebut. Untuk menjaga konsistensi data, transaksi tersebut memastikan bahwa semua operasi yang ada di dalamnya diterapkan ke Datastore sebagai unit atau, jika salah satu operasi gagal, bahwa tidak ada satu pun operasi yang diterapkan. Selain itu, semua pembacaan dengan konsistensi kuat (kueri atau pembacaan ancestor) yang dilakukan dalam transaksi yang sama akan melihat snapshot data yang konsisten.

Seperti yang telah disebutkan di atas, entity group adalah serangkaian entity yang terhubung melalui ancestry ke elemen root yang sama. Pengaturan data ke dalam entity group dapat membatasi transaksi yang dapat dilakukan:

  • Semua data yang diakses oleh transaksi harus berada dalam maksimal 25 entity group.
  • Jika Anda ingin menggunakan kueri dalam transaksi, data Anda harus diatur ke dalam entity group sedemikian rupa sehingga Anda dapat menentukan filter ancestor yang akan cocok dengan data yang tepat.
  • Ada batas throughput operasi tulis sekitar satu transaksi per detik dalam satu entity group. Batasan ini diberikan karena Datastore melakukan replikasi sinkron tanpa master untuk setiap entity group di area geografis yang luas guna memberikan keandalan dan fault tolerance yang tinggi.

Dalam banyak penerapan, Anda dapat menggunakan konsistensi tertunda (yaitu kueri non-ancestor yang mencakup beberapa entity group, yang terkadang menampilkan data yang sedikit usang) saat mengambil pandangan yang luas tentang data yang tidak terkait, kemudian menggunakan konsistensi yang kuat (kueri ancestor atau get dari satu entity) saat melihat atau mengedit satu set data yang sangat terkait. Dalam penerapan semacam itu, sebaiknya Anda menggunakan entity group yang terpisah untuk setiap set data yang sangat terkait. Untuk informasi selengkapnya, lihat Membuat Struktur untuk Konsistensi Kuat.

Properti dan jenis nilai

Nilai data yang terkait dengan entity terdiri dari satu atau beberapa properti. Setiap properti memiliki nama dan satu atau beberapa nilai. Properti dapat memiliki nilai yang berisi lebih dari satu jenis, dan dua entity dapat memiliki nilai dari jenis yang berbeda untuk properti yang sama. Properti dapat diindeks atau tidak diindeks (kueri yang mengurutkan atau memfilter pada properti P akan mengabaikan entity jika P tidak diindeks). Entity dapat memiliki maksimal 20.000 properti yang diindeks.

Jenis nilai berikut ini didukung:

Jenis nilai Jenis Java Tata urutan Notes
Bilangan Bulat short
int
long
java.lang.Short
java.lang.Integer
java.lang.Long
Angka Disimpan sebagai bilangan bulat panjang, lalu dikonversi ke jenis kolom

Nilai di luar rentang yang berlebih
Angka floating-point float
double
java.lang.Float
java.lang.Double
Angka Presisi ganda 64-bit,
IEEE 754
Boolean boolean
java.lang.Boolean
false<true
String teks (singkat) java.lang.String Unicode Hingga 1.500 byte

Nilai yang lebih besar dari 1.500 byte menampilkan IllegalArgumentException
String teks (panjang) com.google.appengine.api.datastore.Text Tidak ada Hingga 1 megabyte

Tidak diindeks
String byte (pendek) com.google.appengine.api.datastore.ShortBlob Urutan byte Hingga 1.500 byte

Nilai yang lebih panjang dari 1.500 byte menampilkan IllegalArgumentException
String byte (panjang) com.google.appengine.api.datastore.Blob Tidak ada Hingga 1 megabyte

Tidak diindeks
Tanggal dan waktu java.util.Date Kronologis
Titik geografis com.google.appengine.api.datastore.GeoPt Berdasarkan lintang,
lalu bujur
Alamat pos com.google.appengine.api.datastore.PostalAddress Unicode
Nomor telepon com.google.appengine.api.datastore.PhoneNumber Unicode
Alamat email com.google.appengine.api.datastore.Email Unicode
Pengguna Akun Google com.google.appengine.api.users.User Alamat email
berdasarkan urutan Unicode
Handle fitur pesan instan com.google.appengine.api.datastore.IMHandle Unicode
Link com.google.appengine.api.datastore.Link Unicode
Kategori com.google.appengine.api.datastore.Category Unicode
Rating com.google.appengine.api.datastore.Rating Angka
Kunci Datastore com.google.appengine.api.datastore.Key
atau objek yang direferensikan (sebagai turunan)
Menurut elemen jalur
(jenis, ID,
jenis, ID...)
Hingga 1.500 byte

Nilai yang lebih panjang dari 1.500 byte menampilkan IllegalArgumentException
Kunci Blobstore com.google.appengine.api.blobstore.BlobKey Urutan byte
Entity tersemat com.google.appengine.api.datastore.EmbeddedEntity Tidak ada Tidak diindeks
Null null Tidak ada

Penting: Sebaiknya jangan menyimpan users.User sebagai nilai properti karena nilai ini menyertakan alamat email beserta ID uniknya. Jika pengguna mengubah alamat emailnya dan Anda membandingkan user.User lamanya yang tersimpan dengan nilai user.User yang baru, nilai tersebut tidak akan cocok. Sebagai gantinya, gunakan nilai ID pengguna User sebagai ID unik stabil pengguna.

Untuk string teks dan data biner yang tidak dienkode (string byte), Datastore mendukung dua jenis nilai:

  • String pendek (hingga 1.500 byte) diindeks dan dapat digunakan dalam kondisi filter kueri dan tata urutan.
  • String panjang (hingga 1 megabyte) tidak diindeks serta tidak dapat digunakan dalam filter kueri dan tata urutan.
Catatan: Jenis string byte panjang diberi nama Blob di Datastore API. Jenis ini tidak terkait dengan blob seperti yang digunakan dalam Blobstore API.

Jika kueri melibatkan properti dengan nilai jenis campuran, Datastore akan menggunakan pengurutan deterministik berdasarkan representasi internal:

  1. Nilai null
  2. Angka fixed-point
    • Bilangan bulat
    • Tanggal dan waktu
    • Rating
  3. Nilai boolean
  4. Urutan byte
    • String byte
    • String Unicode
    • Kunci Blobstore
  5. Angka floating-point
  6. Titik geografis
  7. Pengguna Akun Google
  8. Kunci Datastore

Karena string teks panjang, string byte panjang, dan entitas tersemat tidak diindeks, urutannya juga tidak ditentukan.

Bekerja dengan entity

Aplikasi dapat menggunakan Datastore API untuk membuat, mengambil, memperbarui, dan menghapus entity. Jika aplikasi mengetahui kunci lengkap untuk suatu entity (atau dapat memperolehnya dari kunci, jenis, dan ID parent-nya), aplikasi dapat menggunakan kunci tersebut untuk beroperasi secara langsung pada entity. Aplikasi juga bisa memperoleh kunci entity sebagai hasil dari kueri Datastore; lihat halaman Kueri Datastore untuk informasi selengkapnya.

Datastore API Java menggunakan metode antarmuka DatastoreService untuk beroperasi pada entity. Anda mendapatkan objek DatastoreService dengan memanggil metode statis DatastoreServiceFactory.getDatastoreService():

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Membuat entity

Anda dapat membuat entity baru dengan membuat instance class Entity, yang menyediakan jenis entity sebagai argumen ke konstruktor Entity().

Setelah mengisi properti entity jika perlu, Anda menyimpannya ke datastore dengan meneruskannya sebagai argumen ke metode DatastoreService.put(). Anda dapat menentukan nama kunci entity dengan meneruskannya sebagai argumen kedua ke konstruktor:

Entity employee = new Entity("Employee", "asalieri");
// Set the entity properties.
// ...
datastore.put(employee);

Jika Anda tidak memberikan nama kunci, Datastore akan otomatis menghasilkan ID numerik untuk kunci entity tersebut:

Entity employee = new Entity("Employee");
// Set the entity properties.
// ...
datastore.put(employee);

Mengambil entity

Untuk mengambil entity yang diidentifikasi oleh kunci tertentu, teruskan objek Key ke metode DatastoreService.get():

// Key employeeKey = ...;
Entity employee = datastore.get(employeeKey);

Memperbarui entity

Untuk memperbarui entity yang ada, ubah atribut objek Entity, lalu teruskan ke metode DatastoreService.put(). Data objek akan menimpa entity yang ada. Seluruh objek dikirim ke Datastore pada setiap panggilan ke put().

Menghapus entity

Dengan mempertimbangkan kunci entity, Anda dapat menghapus entity dengan metode DatastoreService.delete():

// Key employeeKey = ...;
datastore.delete(employeeKey);

Properti berulang

Anda dapat menyimpan beberapa nilai dalam satu properti.

Entity employee = new Entity("Employee");
ArrayList<String> favoriteFruit = new ArrayList<String>();
favoriteFruit.add("Pear");
favoriteFruit.add("Apple");
employee.setProperty("favoriteFruit", favoriteFruit);
datastore.put(employee);

// Sometime later
employee = datastore.get(employee.getKey());
@SuppressWarnings("unchecked") // Cast can't verify generic type.
    ArrayList<String> retrievedFruits = (ArrayList<String>) employee
    .getProperty("favoriteFruit");

Entity tersemat

Terkadang mungkin lebih nyaman untuk menyematkan satu entity sebagai properti entity lain. Hal ini dapat berguna, misalnya, untuk membuat struktur hierarki nilai properti dalam entity. Class Java EmbeddedEntity memungkinkan Anda melakukan ini:

// Entity employee = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678");
embeddedContactInfo.setProperty("phoneNumber", "555-555-5555");
embeddedContactInfo.setProperty("emailAddress", "test@example.com");

employee.setProperty("contactInfo", embeddedContactInfo);

Saat entity tersemat disertakan dalam indeks, Anda dapat membuat kueri pada sub-properti. Jika Anda mengecualikan entity tersemat dari pengindeksan, semua sub-properti juga akan dikecualikan dari pengindeksan. Jika ingin, Anda dapat mengaitkan kunci dengan entity tersemat, tetapi (tidak seperti entity lengkap), kunci tersebut tidak diperlukan dan, meskipun ada, tidak dapat digunakan untuk mengambil entity tersebut.

Daripada mengisi properti entity tersemat secara manual, Anda dapat menggunakan metode setPropertiesFrom() untuk menyalinnya dari entity yang sudah ada:

// Entity employee = ...;
// Entity contactInfo = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original.
embeddedContactInfo.setPropertiesFrom(contactInfo);

employee.setProperty("contactInfo", embeddedContactInfo);

Anda dapat menggunakan metode yang sama di lain waktu untuk memulihkan entity asli dari entity tersemat:

Entity employee = datastore.get(employeeKey);
EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo");

Key infoKey = embeddedContactInfo.getKey();
Entity contactInfo = new Entity(infoKey);
contactInfo.setPropertiesFrom(embeddedContactInfo);

Operasi batch

Metode DatastoreService put(), get(), dan delete() (beserta pasangan AsyncDatastoreService-nya) ) memiliki versi batch yang menerima objek iterable (dari class Entity untuk put(), Key untuk get() dan delete()) lalu menggunakannya untuk beroperasi di beberapa entity dalam satu panggilan Datastore:

Entity employee1 = new Entity("Employee");
Entity employee2 = new Entity("Employee");
Entity employee3 = new Entity("Employee");
// ...

List<Entity> employees = Arrays.asList(employee1, employee2, employee3);
datastore.put(employees);

Operasi batch ini mengelompokkan semua entity atau kunci berdasarkan entity group, lalu melakukan operasi yang diminta pada setiap grup entity secara paralel. Panggilan batch tersebut akan lebih cepat daripada membuat panggilan terpisah untuk setiap entity individual, karena panggilan tersebut menimbulkan overhead hanya untuk satu panggilan layanan. Jika ada beberapa entity group yang terlibat, pekerjaan untuk semua grup akan dilakukan secara paralel di sisi server.

Membuat kunci

Aplikasi dapat menggunakan class KeyFactory untuk membuat objek Key bagi entity dari komponen yang diketahui, seperti jenis dan ID entity. Untuk entity tanpa induk, teruskan jenis dan ID (baik string nama kunci maupun ID numerik) ke metode statis KeyFactory.createKey() untuk membuat kunci. Contoh berikut membuat kunci untuk entity jenis Person dengan nama kunci "GreatGrandpa" atau ID numerik 74219:

Key k1 = KeyFactory.createKey("Person", "GreatGrandpa");
Key k2 = KeyFactory.createKey("Person", 74219);

Jika kunci menyertakan komponen jalur, Anda dapat menggunakan class bantuan KeyFactory.Builder untuk membangun jalur. Metode addChild class ini menambahkan satu entity ke jalur dan menampilkan builder itu sendiri sehingga Anda dapat merangkai serangkaian panggilan, dimulai dengan root entity, untuk membangun jalur satu entity pada satu waktu. Setelah membangun jalur lengkap, panggil getKey untuk mengambil kunci yang dihasilkan:

Key k =
    new KeyFactory.Builder("Person", "GreatGrandpa")
        .addChild("Person", "Grandpa")
        .addChild("Person", "Dad")
        .addChild("Person", "Me")
        .getKey();

Class KeyFactory juga menyertakan metode statis keyToString dan stringToKey untuk melakukan konversi antara kunci dan representasi string-nya:

String personKeyStr = KeyFactory.keyToString(k);

// Some time later (for example, after using personKeyStr in a link).
Key personKey = KeyFactory.stringToKey(personKeyStr);
Entity person = datastore.get(personKey);

Representasi string kunci adalah "web-safe": tidak berisi karakter yang dianggap spesial dalam HTML atau URL.

Menggunakan daftar kosong

Datastore secara historis tidak memiliki representasi untuk properti yang mewakili daftar kosong. Java SDK mengatasi masalah ini dengan menyimpan koleksi kosong sebagai nilai null, sehingga tidak ada cara untuk membedakan antara nilai null dan daftar kosong. Untuk mempertahankan kompatibilitas mundur, perilaku ini tetap menjadi perilaku default, yang disimpulkan sebagai berikut:

  • Properti null ditulis sebagai null ke Datastore
  • Koleksi kosong ditulis sebagai null ke Datastore
  • Nilai null dibaca sebagai null dari Datastore
  • Koleksi kosong dibaca sebagai null.

Namun, jika Anda mengubah perilaku default, SDK untuk Java akan mendukung penyimpanan daftar kosong. Sebaiknya pertimbangkan dampak dari perubahan perilaku default aplikasi, lalu aktifkan dukungan untuk daftar kosong.

Untuk mengubah perilaku default agar Anda dapat menggunakan daftar kosong, tetapkan properti DATASTORE_EMPTY_LIST_SUPPORT selama inisialisasi aplikasi sebagai berikut:

System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());

Dengan properti ini ditetapkan ke true, seperti yang ditunjukkan di atas:

  • Properti null ditulis sebagai null ke Datastore
  • Koleksi kosong ditulis sebagai daftar kosong untuk Datastore
  • Nilai null dibaca sebagai null dari Datastore
  • Saat membaca dari Datastore, daftar kosong akan ditampilkan sebagai Koleksi kosong.