#summary Defining and registering entities
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 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 way a field is stored, 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 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.
* You can persist any of the [http://code.google.com/appengine/docs/java/datastore/dataclasses.html#Core_Value_Types core value types], Collections (ie Lists and Sets) of the core value types, or arrays of the core value types. You can also persist properties of type {{{Key}}} (the native datastore type) and {{{Key>}}}) (the Objectify generified version). There are more advanced types that can be persisted which will be discussed later.
* 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.
== 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.EntitySubclass;
import com.googlecode.objectify.annotation.Id;
@Entity
public class Animal {
@Id Long id;
String name;
}
@EntitySubclass(index=true)
public class Mammal extends Animal {
boolean longHair;
}
@EntitySubclass(index=true)
public class Cat extends Mammal {
boolean hypoallergenic;
}
}}}
Things to note:
* The root of your polymorphic hierarchy *must* be annotated with {{{@Entity}}}.
* All polymorphic subclasses must be annotated with {{{@EntitySubclass}}}.
* You can skip {{{@EntitySubclass}}} on intermediate classes which will never be materialized or queried for.
* You should register all classes in the hierarchy separately, but order is not important.
* Polymorphism applies only to entities, not to @Embed classes.
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";
m.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 all = ofy().load().type(Animal.class);
// This query will produce the Mammal and Cat
Query mammals = ofy().load().type(Mammal.class);
}}}
=== Polymorphism Native Representation ===
When you store a polymorphic entity subclass your entity is 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 {{{@EntitySubclass(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.
There are two ways you can affect this:
# You can control indexing of subclasses by specifying {{{@EntitySubclass(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 {{{@EntitySubclass(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().
== Embedding ==
Objectify supports embedded classes and collections of embedded classes. This allows you to store structured data within a single POJO entity in a way that remains queryable. With a few limitations, this can be an excellent replacement for storing JSON data.
*IMPORTANT!* Read the note about changes in the [Entities#Embedding_Native_Representation native storage representation].
=== Embedded Classes ===
You can nest objects to any arbitrary level.
{{{
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
@Embed
class LevelTwo {
String bar;
}
@Embed
class LevelOne {
String foo;
LevelTwo two
}
@Entity
class EntityWithEmbedded {
@Id Long id;
LevelOne one;
}
}}}
=== Embedded Collections and Arrays ===
You can place {{{@Embed}}} objects in collections and arrays:
{{{
@Entity
class EntityWithEmbeddedCollection {
@Id Long id;
List ones = new ArrayList();
}
}}}
Some things to keep in mind:
* This does not support two-dimensional structures of any kind.
* You cannot nest {{{@Embed}}} arrays/collections inside other {{{@Embed}}} arrays/collections.
* You cannot put arrays/collections of native types inside {{{@Embed}}} arrays/collections.
* You can, however, nest {{{@Embed}}} arrays/collections inside any number of {{{@Embed}}} classes.
* You should initialize collections. Null or empty collections are not written to the datastore and therefore get ignored during load. Furthermore, the concrete instance will be used as-is, allowing you to initialize collections with Comparators or other state.
=== Embedding Native Representation ===
*VERY IMPORTANT!*
The native storage format for @Embed structures is changing between Objectify 4.X and 5.X. Objectify 4.1 is a transitional version which can read and write both formats. To avoid surprises, it defaults to writing the old format.
You are _strongly_ encouraged to migrate to the new format now. Objectify 4.1 will automatically detect the correct format when loading entities. To migrate your data, call `ObjectifyFactory.setSaveWithNewEmbedFormat(true)` and load+save every entity which has an @Embed structure.
Migration to the new storage format _cannot be reverted_. If you migrate and then revert to a previous version of Objectify which does not recognize the new format, *you will lose data*.
Starting with Objectify 5.0, the legacy storage format will be dropped; it is incompatible with many new features (such as embedded object polymorphism). Start this migration now!
Here's the nitty gritty detail of how {{{@Embed}}} objects are persisted so that you an access them through the Low-Level API.
==== New Format For 4.1+ ====
For Objectify 4.1 you must explicitly enable save in the new format by calling `ObjectifyFactory.setSaveWithNewEmbedFormat(true)`. Objectify 5.0 will only support this format.
The new format is very simple. Objects are stored as `EmbeddedEntity`, a new map-like container that was recently added to the Low-Level API. Collections of objects are simply collections of `EmbeddedEntity` objects.
However, GAE does not natively support indexing of `EmbeddedEntity` fields. To allow you to query on embedded data, Objectify creates synthetic index properties in the top-level Entity objects. These are dot-separated property names that have the exact same structure as the old legacy format, so your query code need not change when upgrading to the new format. However, these indexes are only used for queries and are not used to load your POJO objects.
The appearance of your entities in the GAE console Datastore Viewer will change. Unfortunately, the Datastore Viewer currently shows `EmbeddedEntity` objects and collections of `EmbeddedEntity` objects as opaque blobs. Please [https://code.google.com/p/googleappengine/issues/detail?id=10737 star this issue].
==== Legacy Format For 4.0 And Previous ====
This is the default format for Objectify 4.X. You are _strongly_ encouraged to migrate to the new format by upgrading to Objectify 4.1, calling `ObjectifyFactory.setSaveWithNewEmbedFormat(true)` and load+saving every entity which has an @Embed structure.
Here is an example of the native storage format, given the example data structures we have discussed already:
{{{
EntityWithEmbedded ent = new EntityWithEmbedded();
ent.one = new LevelOne();
ent.one.foo = "Foo Value";
ent.one.two = new LevelTwo();
ent.one.two.bar = "Bar Value";
ofy().save().entity(ent);
}}}
This will produce an entity that contains:
|| one.foo || "Foo Value" ||
|| one.two.bar || "Bar Value" ||
For collections and arrays of {{{@Embed}}} classes, the storage mechanism is more complicated:
{{{
EntityWithEmbeddedCollection ent = new EntityWithEmbeddedCollection();
for (int i=1; i<=4; i++) {
LevelOne one = new LevelOne();
one.foo = "foo" + i;
one.two = new LevelTwo();
one.two.bar = "bar" + i;
ent.ones.add(one);
}
ofy().save().entity(ent);
}}}
This will produce an entity that contains:
|| ones.foo || {{{ ["foo1", "foo2", "foo3", "foo4"] }}} ||
|| ones.two.bar || {{{ ["bar1", "bar2", "bar3", "bar4"] }}} ||
This is what the entity would look like if the second and third values in the {{{ones}}} collection were {{{null}}}:
|| ones.foo^null || {{{ [1, 2] }}} ||
|| ones.foo || {{{ ["foo1", "foo4"] }}} ||
|| ones.two.bar || {{{ ["bar1", "bar4"] }}} ||
The synthetic ^null property only exists if the collection contains nulls. It is never indexed.
== Serializing ==
An alternative to {{{@Embed}}} is to {{{@Serialize}}}, which will let you store nearly any Java object graph.
{{{
@Entity
class EntityWithSerialized {
@Id Long id;
@Serialize Map