Modeling Entity Relationships in Google Cloud Datastore
Google Cloud Datastore is a NoSQL, schemaless database. Unlike traditional relational databases (RDBMS), it does not have foreign keys or joins. Instead, relationships between entities are modeled in two primary ways:
- Storing Keys: An entity can store the
Keyof one or more other entities as a property. This is the most common and flexible approach. - Ancestor Paths: Entities can be created in a hierarchy, where one entity is the parent of another. This creates a strong relationship within an "entity group" and allows for strongly consistent queries.
This guide focuses on modeling relationships by storing keys, using the official Google Cloud Datastore client library for Java.
One-to-One Relationships
In a one-to-one relationship, one entity is directly related to exactly one other entity. This is modeled by storing the Key of the related entity as a property.
Example: A User has one UserProfile.
// User.java
import com.google.cloud.datastore.Key;
import com.google.cloud.datastore.Entity;
// Assume this class is mapped to a "User" entity
public class User {
private String username;
private Key userProfileKey; // Stores the Key of the UserProfile entity
// Getters and setters...
}
// UserProfile.java
// Assume this class is mapped to a "UserProfile" entity
public class UserProfile {
private String fullName;
private String bio;
// Getters and setters...
}
Usage:
To link the two, you first save the UserProfile, get its generated Key, and then set that key on the User entity before saving it.
// ds is an instance of com.google.cloud.datastore.Datastore
Datastore datastore = DatastoreOptions.getDefaultInstance().getService();
KeyFactory userProfileKeyFactory = datastore.newKeyFactory().setKind("UserProfile");
// 1. Create and save the UserProfile
FullEntity<IncompleteKey> userProfileEntity = Entity.newBuilder(userProfileKeyFactory.newKey())
.set("fullName", "Jane Doe")
.set("bio", "Software Engineer")
.build();
Entity savedUserProfile = datastore.add(userProfileEntity);
Key userProfileKey = savedUserProfile.getKey();
// 2. Create the User and link it to the UserProfile's Key
KeyFactory userKeyFactory = datastore.newKeyFactory().setKind("User");
FullEntity<IncompleteKey> userEntity = Entity.newBuilder(userKeyFactory.newKey())
.set("username", "jane.doe")
.set("userProfileKey", userProfileKey) // Set the relationship
.build();
datastore.add(userEntity);
One-to-Many Relationships
In a one-to-many relationship, one entity is related to multiple other entities. This is modeled by storing a List of Key objects.
Example: An Author can write many Books.
// Author.java
import com.google.cloud.datastore.Key;
import java.util.List;
// Mapped to an "Author" entity
public class Author {
private String name;
private List<Key> bookKeys; // A list of Keys for all books by this author
// Getters and setters...
}
// Book.java
// Mapped to a "Book" entity
public class Book {
private String title;
private int publishYear;
// Getters and setters...
}
Usage:
You save each Book, collect their keys, and store the list of keys in the Author entity.
Datastore datastore = DatastoreOptions.getDefaultInstance().getService();
KeyFactory bookKeyFactory = datastore.newKeyFactory().setKind("Book");
KeyFactory authorKeyFactory = datastore.newKeyFactory().setKind("Author");
// 1. Create and save multiple Book entities
Entity book1 = datastore.add(Entity.newBuilder(bookKeyFactory.newKey()).set("title", "The Datastore Guide").build());
Entity book2 = datastore.add(Entity.newBuilder(bookKeyFactory.newKey()).set("title", "Advanced Cloud Java").build());
// 2. Create the Author and link it to the book keys
List<Key> bookKeys = List.of(book1.getKey(), book2.getKey());
FullEntity<IncompleteKey> authorEntity = Entity.newBuilder(authorKeyFactory.newKey())
.set("name", "John Smith")
.set("bookKeys", bookKeys) // Set the list of keys
.build();
datastore.add(authorEntity);
// To fetch the books for an author, you would first get the author,
// then use the list of keys to fetch the books in a batch.
Many-to-Many Relationships
A many-to-many relationship is modeled by having both entities store a List of Keys pointing to each other.
Example: A Student can enroll in many Courses, and a Course can have many Students.
// Student.java
import com.google.cloud.datastore.Key;
import java.util.List;
// Mapped to a "Student" entity
public class Student {
private String studentName;
private List<Key> courseKeys;
// Getters and setters...
}
// Course.java
// Mapped to a "Course" entity
public class Course {
private String courseTitle;
private List<Key> studentKeys;
// Getters and setters...
}
Usage:
When a student enrolls in a course, you must update both entities. This should be done within a single transaction to ensure data consistency.
Datastore datastore = DatastoreOptions.getDefaultInstance().getService();
// Assume studentKey and courseKey are the keys of existing entities
Key studentKey = ...;
Key courseKey = ...;
Transaction txn = datastore.newTransaction();
try {
// Get both entities
Entity student = txn.get(studentKey);
Entity course = txn.get(courseKey);
// Get their current key lists (handle nulls if they are new)
List<Value<Key>> studentCourseKeys = student.getList("courseKeys");
List<Value<Key>> courseStudentKeys = course.getList("studentKeys");
// Update both entities with the new relationship
Entity updatedStudent = Entity.newBuilder(student)
.set("courseKeys", List.of(studentCourseKeys, courseKey))
.build();
Entity updatedCourse = Entity.newBuilder(course)
.set("studentKeys", List.of(courseStudentKeys, studentKey))
.build();
// Save both entities in the transaction
txn.put(updatedStudent, updatedCourse);
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}