Entities - objectify/objectify GitHub Wiki

First define your domain model in Java using Objectify's annotations, then register these classes with the ObjectifyFactory.

Defining Entities

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.

The Basics

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 the transient 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 the Key which identifies an entity.

  • The @Id field can be of type Long, long, or String. If you use Long 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 use String or the primitive long 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 to Text internally. Note that Text fields, like Blob fields, are never indexed (see Queries).

  • byte[] fields are automatically converted to Blob 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.

Embedded Classes

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.

Embedding Entities

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.

@Container fields

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

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[]

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.

Maps

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.

@Stringify

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.

@Mapify

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.

Polymorphism

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 and Embedded Objects

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.

Polymorphism Native Representation

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:

  1. 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.
  2. 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().

Serializing

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;
}

Compression

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.

Relationships

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.

Keys

There are two different Key classes in Objectify:

  1. com.google.cloud.datastore.Key, usually referred to as Key
  2. com.googlecode.objectify.Key, usually referred to as Key<?>

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.

Ref<?>s

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.

@Load

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.

Hiding Ref<?>

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); }
}

Additional Ref<?> Considerations

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

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)
  • The name of the field is irrelevant and can be changed at any time without modifying stored data.
  • The @Parent field can be Key, Key<?>, or Ref<?>.
  • @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.

One-To-Many Relationships

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 Keys, Key<?>s, and Ref<?>s. You can use @Load annotations with Ref<?>s.

There are some considerations:

  • Every time you load() or save() 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);

One-To-Many Alternative

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.

Registering Entities

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.

⚠️ **GitHub.com Fallback** ⚠️