Entities - objectify/objectify GitHub Wiki
First define your domain model in Java using Objectify's annotations, then register these classes with the ObjectifyFactory
.
Entities are simple Java POJOs with a handful of special annotations. Objectify has its own annotations and does NOT use JPA or JDO annotations.
Note that throughout this documentation we will leave off getter and setter methods for brevity.
Here is a minimal entity class:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Ignore;
@Entity
public class Car {
@Id Long id;
String vin;
int color;
byte[] rawData;
@Ignore int irrelevant;
private Car() {}
public Car(String vin, int color) {
this.vin = vin;
this.color = color;
}
}
Observe:
-
Entity classes must be annotated with
@Entity
. -
Objectify persists fields and only fields. It does not arbitrarily map fields to the datastore; if you want to change the name of a property in the datastore, rename the field. Getters and setters are ignored so you can isolate the public interface of your class (eg,
public String getVehicleIdentificationNumber() { return vin;
}). -
Objectify will not persist
static
fields,final
fields, or fields annotated with@Ignore
. It will persist fields with thetransient
keyword, which only affects serialization. -
Entities must have one field annotated with
@Id
. The actual name of the field is irrelevant and can be renamed at any time, even after data is persisted. This value (along with the kind 'Car') becomes part of theKey
which identifies an entity. -
The
@Id
field can be of typeLong
,long
, orString
. If you useLong
and save an entity with a null id, a numeric value will be generated for you using the standard GAE allocator for this kind. If you useString
or the primitivelong
type, values will never be autogenerated. -
There must be a no-arg constructor (or no constructors - Java creates a default no-arg constructor). The no-arg constructor can have any protection level (private, public, etc).
-
String
fields which store more than 500 characters (the GAE limit) are automatically converted toText
internally. Note thatText
fields, likeBlob
fields, are never indexed (see Queries). -
byte[]
fields are automatically converted toBlob
internally. However,Byte[]
is persisted "normally" as an array of (potentially indexed)Byte
objects. Note that GAE natively stores all integer values as a 64-bit long.
Objectify allows you to nest embedded classes:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
class Engine {
float displacement;
}
@Entity
class Car {
@Id Long id;
Engine engine;
}
Any class which is not recognized as a core value type or a special Objectify type (like Key<?>
) will be broken down into its component fields and stored natively as an embedded Entity. There are no limitations on how deep you can nest embedded structures. Embedded classes are treated just like entities - they can be polymorphic and have lifecycle methods.
There is a special case when you embed a class which is itself an @Entity
:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
@Entity
class Person {
@Id Long id;
}
@Entity
class Car {
@Id Long id;
Person person;
}
This will not create a foreign key reference! This will embed the Person
entity in the Car
entity. The key of the Person
will be used as the native Key
stored in the embedded entity. To create relationships between entities, see the later section on Relationships.
If you place @Container
on a field in an embedded class, Objectify will populate it with a reference to the appropriate containing class:
import com.googlecode.objectify.annotation.Container;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
class Engine {
float displacement;
@Container Car car;
}
@Entity
class Car {
@Id Long id;
Engine engine;
}
In deeply nested structures, @Container
fields will be populated with the first structure found going "up" that matches the correct type.
@Container
fields are not persisted in the native datastore. They are populated only when the entity is loaded.
Collections and arrays are embedded normally, and can contain nested classes. You are encouraged to initialize collections in your constructor:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
class Tire {
String position;
float treadRemaining;
}
@Entity
class Car {
@Id Long id;
SortedSet<Tire> tires = new TreeSet<>(new TreadRemainingComparator());
}
If you initialize collections like this, Objectify will clear and recycle the collection when loading entities. This will preserve comparators or any other state relevant to the collection. If you do not initialize collections, Objectify will take a best guess at the appropriate type based on the field (eg List
will be initialized to ArrayList
).
Arrays cannot be initialized in this manner.
byte[]
is treated specially - it is automatically converted to and from native Blob representation in the datastore. Other arrays of number types (including Byte) are stored natively as collections.
Objectify will store a Map<String, ?>
as an embedded Entity in the datastore:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
@Entity
class Car {
@Id Long id;
Map<String, Person> occupants = new HashMap<>();
}
The value can be any type that can be persisted as a field, including basic types, embedded objects, and collections. Note that because this is stored as Entity
, which is based on a HashMap
, ordering of map values is unpredictable - even if your field is of type LinkedHashMap
.
In order to use non-String keys with Maps, you may specify the @Stringify
annotation:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Stringify;
import com.googlecode.objectify.stringifier.Stringifier;
class DateStringifier implements Stringifier<LocalDate> {
@Override
public String toString(LocalDate obj) {
return obj.getString();
}
@Override
public LocalDate fromString(String str) {
return new LocalDate(str);
}
}
@Entity
class Car {
@Id Long id;
@Stringify(DateStringifier.class)
Map<LocalDate, ServiceRecord> serviceHistory = new HashMap<>();
}
Objectify automatically detects and stringifies Key<?>
and Enum
, so you don't need to explicitly specify a Stringifier
for these types. Key<?>
is converted to the web safe string representation.
Perhaps you have a native collection of embedded objects which you would prefer to access as a Map:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.mapper.Mapper;
class RecordDateMapper implements Mapper<LocalDate, ServiceRecord> {
@Override
public LocalDate getKey(ServiceRecord value) {
return value.getDate();
}
}
@Entity
class Car {
@Id Long id;
@Mapify(RecordDateMapper.class)
Map<LocalDate, ServiceRecord> serviceHistory = new HashMap<>();
}
The serviceHistory
field will be stored natively as a List<Entity>
. When Objectify loads this field, it will use the Mapper
to obtain a key for each object and store the entry in the map.
The value of this map can be any type that can be persisted by Objectify.
Objectify lets you define a polymorphic hierarchy of related entity classes, and then load and query them without knowing the specific subtype. Here are some examples:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Subclass;
import com.googlecode.objectify.annotation.Id;
@Entity
public class Animal {
@Id Long id;
String name;
}
@Subclass(index=true)
public class Mammal extends Animal {
boolean longHair;
}
@Subclass(index=true)
public class Cat extends Mammal {
boolean hypoallergenic;
}
Things to note:
- All polymorphic subclasses must be annotated with
@Subclass
and registered explicitly. - You can skip
@Subclass
on intermediate classes which will never be materialized or queried for. - If you save an object of a type which is not a registered
@Subclass
, you will get an exception!
In a polymorphic hierarchy, you can load()
without knowing the actual type:
Animal annie = new Animal();
annie.name = "Annie";
ofy().save().entity(annie).now();
Mammal mam = new Mammal();
mam.name = "Mam";
mam.longHair = true;
ofy().save().entity(mam).now();
Cat nyan = new Cat();
nyan.name = "Nyan";
nyan.longHair = true;
nyan.hypoallergenic = true;
ofy().save().entity(nyan).now();
// This will return the Cat
Animal fetched = ofy().load().type(Animal.class).id(nyan.id).now();
// This query will produce three objects, the Animal, Mammal, and Cat
Query<Animal> all = ofy().load().type(Animal.class);
// This query will produce the Mammal and Cat
Query<Mammal> mammals = ofy().load().type(Mammal.class);
Polymorphism applies to embedded objects exactly as it does to top level @Entity
objects.
- You do not need
@Entity
at the root of your polymorphic hierarchy (unless you actually want to embed an entity with a real key). Objectify discovers the root of your polymorphic hierarchy when the field is declared. - You must register all
@Subclass
classes which will be used. - The
index
element of@Subclass
is ignored.
Subclasses of polymorphic objects are stored with two additional, hidden synthetic properties:
-
^d holds a discriminator value for the concrete class type. This defaults to the class shortname but can be modified with the
@Subclass(name="Alternate")
annotation. - ^i holds an indexed list of all the discriminators relavant to a class; for example a Cat would have "Mammal", "Cat". Note that subclasses are not indexed by default. See below.
The indexed property is what allows polymorphic queries to work. It also means that you cannot simply change your hierarchy arbitrarily and expect queries to continue to work as expected - you may need to re-save() all affected entities to rewrite the indexed field.
Note that classes without the @Subclass
annotation (ie, the base type) will not have these synthetic properties. They will be treated as the base type.
There are two ways you can affect this:
- You can control indexing of subclasses by specifying
@Subclass(index=true)
. Subclasses are not indexed by default, you must explicitly enable this for each subclass. Note that this only affects queries; you can always get-by-key or get-by-id and receive the proper typed object irrespective of indexing. - You can use
@Subclass(alsoLoad="OldDiscriminator")
to "reclaim" old discriminator values when changing class names. Note that this will not help with query indexes, which must be re-saved().
An alternative way to embed complex structures is to @Serialize
, which will let you store nearly any Java object graph.
@Entity
class EntityWithSerialized {
@Id Long id;
@Serialize Map<Object, Object> stuff;
}
There are some limitations:
- All objects stored in the graph must follow Java serialization rules, including implement
java.io.Serializable
. - The total size of an entity cannot exceed 1 megabyte. If your serialized data exceeds this size, you will get an exception when you try to
save()
it. - You will not be able to use the field or any child fields in queries.
- As per serializaton rules,
transient
(the java keyword) fields will not be stored. - All Objectify annotations will be ignored within your serialized data structure. This means
@Ignore
fields within your serialized structure will be stored! - Java serialization data is opaque to the datastore viewer and other languages (ie GAE/Python). You will only be able to retrieve your data from Java.
However, there are significant benefits to storing data this way:
- You can store nearly any object graph, including circular object references. If Java can serialize it, you can store it.
- Your field need not be statically typed. Declare
Object
if you want. - Collections can be stored in their full state; for example, a SortedSet will remember its Comparator implementation.
You are strongly advised to place serialVersionUID
on all classes that you intend to store as @Serialize
. Without this, any change to your classes will prevent stored objects from being deserialized on fetch. Example:
class SomeStuff implements Serializable {
/** start with 1 for all classes */
private static final long serialVersionUID = 1L;
String foo;
Object bar;
}
@Entity
class EntityWithSerialized {
@Id Long id;
@Serialize SomeStuff stuff;
}
You can automatically zip the serialized blob:
@Entity
class EntityWithSerialized {
@Id Long id;
@Serialize(zip=true) Map<Object, Object> stuff;
}
You can add or remove zipping at any time. Irrespective of the zip
value, Objectify will read the field correctly whether it was zipped or not in the datastore. Of course, when the entity is saved, the zip
value will determine whether or not the data in that instance is compressed.
A relationship is simply a key stored as a field in an entity. Objectify provides several facilities to make it easier to manage relationships using keys.
There are two different Key classes in Objectify:
-
com.google.cloud.datastore.Key
, usually referred to asKey
-
com.googlecode.objectify.Key
, usually referred to asKey<?>
1 is the native datastore Key
class used by the low-level API. It is the "one true Key" because all datastore operations use this type; Objectify always converts to (or from) the native Key
. However, because this structure lacks generic type information, it should almost never appear in your code.
2 is Objectify's generified version of Key
. It's much less errorprone to use Key<Person>
or Key<Car>
, so you should always use Key<?>
when you use keys directly.
For all intents and purposes, these structures are equivalent. For example, these two classes are identical in the datastore:
import com.google.cloud.datastore.Key;
@Entity
class Thing {
@Id Long id;
Key other;
}
import com.googlecode.objectify.Key;
@Entity
class Thing {
@Id Long id;
Key<Other> other;
}
You can change the type of the other
field at any time, even after data is stored. Remember, the datastore contains the native Key
.
Even Key<?>
s are not very convenient when you are working with graphs of entities. Objectify provides Ref<?>
, which works just like a Key<?>
but allows you to directly access the actual entity object as well:
import com.googlecode.objectify.Ref;
@Entity
class Car {
@Id Long id;
Ref<Person> driver; // Person is an @Entity
}
Car car = new Car();
car.driver = Ref.create(driverKey);
ofy().save().entity(car).now();
Car fetched = ofy().load().entity(car).now();
Person driver = fetched.driver.get();
Ref<?>
fields are stored as native Key
fields in the datastore. You can freely swap between Key
, Key<?>
, and Ref<?>
in your Java data model without modifying stored data.
This provides a nice consistent structure for your object graph, but it's still clunky. Ref<?>
works best in concert with the @Load
annotation.
In the above example, the fetched.driver.get()
caused an immediate fetch from the datastore. This could be a performance problem if you are iterating through lots of Cars loading their drivers; each driver fetch would be a separate request. By using the @Load
annotation, you can load drivers into the session efficiently:
import com.googlecode.objectify.annotation.Load;
@Entity
class Car {
@Id Long id;
@Load Ref<Person> driver; // Person is an @Entity
}
Car car = new Car();
car.driver = Ref.create(driverKey);
ofy().save().entity(car).now();
Car fetched = ofy().load().entity(car).now();
Person driver = fetched.driver.get();
Objectify will translate this into an optimal number of batch fetches. For example:
ofy().load().keys(car1, car2, car3);
This will produce one round of batch fetching for the cars, plus one round of batch fetching for all the drivers, plus one round of fetching for any @Load
fields in all the drivers... and on. Objectify is also smart about not re-fetching anything that has already been fetched in an earlier cycle. This optimized behavior is very hard to write by hand.
Often you will not wish to @Load
every entity relationship every time. @Load
provides the ability to specify conditional load groups. See Load Groups for more.
You will generally find it convenient to encapsulate Ref<?>
behind getter and setter methods:
@Entity
class Car {
@Id Long id;
@Load Ref<Person> driver; // Person is an @Entity
public Person getDriver() { return driver.get(); }
public void setDriver(Person value) { driver = ObjectifyService.ref(value); }
}
Ref<?>
is a reference to a key, but does not itself hold an instance to an entity. Ref<?>
s link to the current session; calling get()
loads the referenced entity out of the session (loading from the datastore if necessary). Think of them as "live" connections to the datastore; if you load a Ref<?>
, then ofy().delete()
the referenced entity, the next call to Ref<?>.get()
will return null.
Parent relationships are described in Concepts. Objectify models them with the @Parent
annotation:
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
@Entity
public class Person {
@Id Long id;
String name;
}
@Entity
public class Car {
@Parent Key<Person> owner;
@Id Long id;
String color;
}
Each Car entity is part of the parent owner's entity group. Important things to note:
- There can only be one
@Parent
field in a class! - Along with the
@Id
and kind, the@Parent
field defines the key (identity) of the entity.- Construct a key to the car like this:
ObjectifyService.key(ownerKey, Car.class, carId)
- Construct a key to the car like this:
- The name of the field is irrelevant and can be changed at any time without modifying stored data.
- The
@Parent
field can beKey
,Key<?>
, orRef<?>
. - @Load annotations work with
Ref<?>
s. As a bonus, the parent will be loaded in the same batch as the initial load.
Note that this example is an inappropriate use of the @Parent entity; if a car were to be sold to a new owner, you would need to delete the Car and create a new one. It is often better to use simple nonparent key references even when there is a conceptual parent-child or owner-object relationship; in that case you could simply change the owner.
VERY IMPORTANT: If you load() an entity, change the @Parent
field, and save() the entity, you will create a new entity. The old entity (with the old parent) will still exist. You cannot simply change the value of a @Parent
field. This is a fundamental aspect of the datastore; @Parent
values form part of an entity's identity.
The datastore can natively persist collections of simple types, including keys. This creates one approach for defining one-to-many (and many-to-many) relationships.
@Entity
public class Employee {
@Id String name;
List<Key<Employee>> subordinates = new ArrayList<Key<Employee>>();
}
This works with Key
s, Key<?>
s, and Ref<?>
s. You can use @Load
annotations with Ref<?>
s.
There are some considerations:
- Every time you
load()
orsave()
an Employee, it will fetch and store the entire list of subordinates keys. Large numbers of subordinates will bloat the entity and impact performance. - App Engine limits you to 5,000 entries in a multi-valued property.
- Because fetches are performed by key, you have a strongly-consistent view of the a manager and subordinates.
If you put @Index
on subordinates, you can issue a query like this:
Iterable<Employee> managers = ofy().load().type(Employee.class).filter("subordinates", fred);
Storing keys in a collection field is a very "appenginey" way of modeling data, and faithfully works with the key/value nature of the datastore. However, this is not the traditional way of modeling one-to-many relationships in a relational database. You can still use the RDBMS approach, but there are caveats:
@Entity
public class Employee {
@Id String name;
@Index Key<Employee> manager;
}
Iterable<Employee> subordinates = ofy().load().type(Employee.class).filter("manager", fred);
- You must issue a separate query for each employee; you cannot batch-fetch the managers of many employees at once.
- Queries are less cache-friendly than direct key references.
The decision of which approach to take will depend heavily on the shape of your data and what queries you need to perform.
Before you use Objectify to load or save data, you must register all entity classes for your application. Objectify will introspect these classes and their annotations to build a metamodel which is used to efficiently manipulate entities at runtime.
ObjectifyService.register(Car.class);
ObjectifyService.register(Motorcycle.class);
- Registration must be done at application startup, before Objectify is used.
- Registration must be single-threaded. Do not register() from multiple threads.
- All entity classes must be registered, including polymorphic subclasses.
- Embedded classes do not need to be registered unless they are polymorphic.
We recommend that you register classes in a servlet context listener at application startup. An example is given in the Setup instructions.
Because this registration process requires loading and introspecting classes, it adds a small delay to your application start time which is directly proportional to the number of classes you register.