ObjectBox - Relations

Prefer to dive right into code? Check the relation project in the example repo.

Objects may reference other objects, for example using a simple reference or a list of objects. In database terms, we call those references relations. The object defining the relation we call the source object, the referenced object we call target object. So the relation has a direction.

If there is one target object, we call the relation to-one. And if there can be multiple target objects, we call it to-many. Relations are lazily initialized: the actual target objects are fetched from the database when they are first accessed. Once the target objects are fetched, they are cached for further accesses.

To-One Relations

To-One Relations

You define a to-one relation using the ToOne class, a smart proxy to the target object. It gets and caches the target object transparently. For example, an order is typically made by one customer. Thus, we could model the Order class to have a to-one relation to the Customer like this:

// Customer.java
@Entity
public class Customer {
@Id public long id;
}
// Order.java
@Entity
public class Order {
@Id public long id;
public ToOne<Customer> customer;
}

Now let’s add a customer and some orders. To set the related customer object, call setTarget() on the ToOne instance and put the order object:

Customer customer = new Customer();
Order order = new Order();
order.customer.setTarget(customer);
long orderId = boxStore.boxFor(Order.class).put(order); // puts order and customer

If the customer object does not yet exist in the database, the ToOne will put it. If it already exists, the ToOne will only create the relation (but not put it). See further below for details about updating relations.

Note: if your related entity uses self-assigned IDs with @Id(assignable = true) it will not be inserted. See below about updating ToOne for details.

To get the customer of an order call getTarget() on the ToOne instance:

Order order = boxStore.boxFor(Order.class).get(orderId);
Customer customer = order.customer.getTarget(customer);

This will do a database call on the first access (lazy loading). It uses lookup by ID, which is very fast in ObjectBox. If you only need the ID instead of the whole target object, call getTargetId() instead. It can be more efficient because it does not touch the database at all.

We can also remove the relationship to a customer:

order.customer.setTarget(null);
boxStore.boxFor(Order.class).put(order);

Note that this does not remove the customer from the database, it just dissolves the relationship.

How ToOne works behind the scenes

If you look at your model in objectbox-models/default.json you can see, a ToOne property is not actually stored. Instead the ID of the target object is saved in a virtual property named like the ToOne property appended with Id.

Expose the ToOne target ID property

You can directly access the target ID property by defining a long property in your entity class with the expected name:

@Entity
public class Order {
@Id public long id;
public long customerId; // ToOne target ID property
public ToOne<Customer> customer;
}

You can change the name of the expected target ID property by adding the @TargetIdProperty(String) annotation to a ToOne.

Initialization Magic

Did you notice that the ToOne field customer was never initialized in the code example above? Why can the code still use customer without any NullPointerException? Because the field actually is initialized – the initialization code just is not visible in your sources.

The ObjectBox Gradle plugin will transform your entity class (only supported for plain Java and Android projects) to do the proper initialization in constructors before your code is executed. Thus, even in your constructor code, you can just assume ToOne and ToMany/ List properties have been initialized and are ready for you to use:

@Entity
public class Example {
ToOne<Order> order;
ToMany<Order> orders;
transient BoxStore __boxStore; // <-- Transform adds this field
public Example() {
// Transform inits ToOne and ToMany in constructors
// not calling another constructor
this.order = new ToOne<>(this, Example_.order);
this.orders = new ToMany<>(this, Example_.orders);
}
public Example(String value) {
this();
// Calls another constructor, so Transform does not add init
}
}

If your setup does not support transformations, like non-Android Kotlin code, add the above modifications yourself. You also will have to call box.attach(entity) before modifying ToOne or ToMany properties.

Improve Performance

To improve performance when ObjectBox constructs your entities, you should provide an all-properties constructor.

For a ToOne you have to add an id parameter, typically named like the ToOne field appended with Id . Check your objectbox-models/default.json file to find the correct name.

An example:

@Entity
public class Order {
@Id public long id;
public ToOne<Customer> customer;
public Order() { /* default constructor */ }
public Order(long id, long customerId /* virtual ToOne id property */) {
this.id = id;
this.customer.setTargetId(customerId);
}
}

To-Many Relations

To define a to-many relation, you can use a property of type List or the ToMany class. As the ToOne class, the ToMany class helps you to keep track of changes and to apply them to the database. If you do not need or want that, use type List and take care of applying database changes yourself.

Note that to-many relations are resolved lazily on the first request, and then cached in the source entity inside the ToMany object. So subsequent calls to the get method of the relation do not query the database.

There is a slight difference if you require a one-to-many (1:N) or many-to-many (N:M) relation. A 1:N relation is like the example above where a customer can have multiple orders, but an order is only associated with a single customer. An example for an N:M relation are students and teachers: students can have classes by several teachers but a teacher can also instruct several students.

One-to-Many (1:N)

One-to-Many (1:N)

To define a one-to-many relation, you need to annotate your relation property with @Backlink. It links back to a to-one relation in the target object. Using the customer and orders example, we can modify the customer class to have a to-many relation to the customers orders:

// Customer.java
@Entity
public class Customer {
@Id public long id;
// 'to' is optional if only one relation matches
@Backlink(to = "customer")
public ToMany<Order> orders;
}
// Order.java
@Entity
public class Order {
@Id public long id;
public ToOne<Customer> customer;
}

The @Backlink annotation tells ObjectBox which ToOne relation to use to populate the list of orders. If there would be multiple to-one relations using Customer inside the Order class, you would need to explicitly specify the name like @Backlink(to = "customer") .

Let’s add some orders together with a new customer. The ToMany implements the Java List interface, so we can simply add orders to it:

Customer customer = new Customer();
customer.orders.add(new Order());
customer.orders.add(new Order());
long customerId = boxStore.boxFor(Customer.class).put(customer); // puts customer and orders

If the order entities do not yet exist in the database, the ToMany will put them. If they already exist, the ToMany will only create the relation (but not put them). See further below for details about updating relations.

Note: if your entities use self-assigned IDs with @Id(assignable = true) the above will not work. See below about updating ToMany for details.

We can easily get the orders of a customer back by accessing the list of orders:

Customer customer = boxStore.boxFor(Customer.class).get(customerId);
for (Order order : customer.orders) {
// TODO
}

Removing orders works as expected:

Order order = customer.orders.remove(0);
boxStore.boxFor(Customer.class).put(customer);
// optional: also remove the order from its box
// boxStore.boxFor(Order.class).remove(order);

Many-to-Many (N:M)

Many-to-Many (N:M)

To define a many-to-many relation you simply add a property using the ToMany class. Assuming a students and teachers example, this is how a simple student class that has a to-many relation to teachers can look like:

// Teacher.java
@Entity
public class Teacher{
@Id public long id;
}
// Student.java
@Entity
public class Student{
@Id public long id;
public ToMany<Teacher> teachers;
}

Adding the teachers of a student works exactly like with a list:

Teacher teacher1 = new Teacher();
Teacher teacher2 = new Teacher();
Student student1 = new Student();
student1.teachers.add(teacher1);
student1.teachers.add(teacher2);
Student student2 = new Student();
student2.teachers.add(teacher2);
// puts students and teachers
boxStore.boxFor(Student.class).put(student1, student2);

If the teacher entities do not yet exist in the database, the ToMany will also put them. If they already exist, the ToMany will only create the relation (but not put them). See further below for details about updating relations.

Note: if your entities use self-assigned IDs with @Id(assignable = true) the above will not work. See below about updating ToMany for details.

To get the teachers of a student we just access the list:

Student student1 = boxStore.boxFor(Student.class).get(student1.id);
for (Teacher teacher : student1.teachers) {
// TODO
}

And if a student drops out of a class, we can remove a teacher:

student1.teachers.remove(0);
// boxStore.boxFor(Student.class).put(student1);
// more efficient than using put:
student1.teachers.applyChangesToDb();

Access Many-To-Many in the reverse direction

Since 2.0.0

Following the above example, you might want an easy way to find out what students a teacher has. Instead of having to perform a query, you can just add a to-many relation to the teacher and annotate it with the @Backlink annotation:

// Teacher.java
@Entity
public class Teacher{
@Id public long id;
@Backlink(to = "teachers") // backed by the to-many relation in Student
public ToMany<Student> students;
}
// Student.java
@Entity
public class Student{
@Id public long id;
public ToMany<Teacher> teachers;
}

Updating Relations

The ToOne and ToMany classes assist you to persist the relation state. They keep track of changes and apply them to the database once you put the entity owning the relation. ObjectBox supports relation updates for new (not yet persisted; ID == 0) and existing (persisted before; ID != 0) entities.

For convenience, ToOne and ToMany will put related entities if they do not yet exist (ID == 0). If they already exist (their ID != 0, or you are using @Id(assignable = true)), only the relation will be created or destroyed. In that case, to put changes to the properties of related entities use their specific Box instead:

// update a related entity using its box
Order orderToUpdate = customer.orders.get(0);
orderToUpdate.text = "Revised description";
// DOES NOT WORK
// boxStore.boxFor(Customer.class).put(customer);
// WORKS
boxStore.boxFor(Order.class).put(orderToUpdate);

Updating ToOne

The ToOne class offers the following methods to update the relation:

  • setTarget(entity) makes the given entity (new or existing) the new relation target; pass null to clear the relation

  • setTargetId(entityId) sets the relation to the given ID of an existing target entity; pass 0 (zero) to clear the relation

  • setAndPutTarget(entity) makes the given entity (new or existing) the new relation target and puts the enclosing entity and if needed the target entity.

order.customer.setTarget(customer); // or order.customer.setCustomerId(customer.getId());
orderBox.put(order);

Note: if your entity was not put yet before calling setAndPutTarget() you need to attach its box first:

Order order = new Order(); // new entity
orderBox.attach(order); // need to attach box first
order.customer.setAndPutTarget(customer);

Note: if you are using self-assigned IDs with @Id(assignable = true) a new target entity will not be stored when storing the parent. Read on for details:

If your target entity uses self-assigned IDs, you have to store it before updating the ToOne relation:

customer.id = 12; // self-assigned id
customerBox.put(customer); // need to put customer first
order.customer.setTarget(customer); // or order.customer.setCustomerId(customer.getId());
orderBox.put(order);

This is because ObjectBox only puts related entities with an ID of 0. See the documentation about IDs for background information.

Updating ToMany

The ToMany class implements the java.lang.List interface while adding change tracking for entities. If you add entities to an ToMany object, those are scheduled to be put in the database. Similarily, if you remove entities from the ToMany object, those are also scheduled to be put. Note that removing entities from the List does not actually remove the entity from the database; just the relation is cleared. Do not forget to put the owning entity to apply changes tracked by ToMany objects to the database.

customer.orders.add(order1);
customer.orders.remove(order2);
customerBox.put(customer);

Note: if you are using self-assigned IDs with @Id(assignable = true) the above will not work. Read on for details:

If your parent entity uses @Id(assignable = true) you need to attach its box before modifying its ToManys:

customer.id = 12; // self-assigned id
customerBox.attach(customer); // need to attach box first
customer.orders.add(order);
customerBox.put(customer);

If your related entity, like Order above, is using self-assigned IDs you need to put the related entities yourself before adding them to a relation:

order.id = 42; // self-assigned id
orderBox.put(order); // need to put order first
customer.orders.add(order);
customerBox.put(customer); // put customer, add relation to order

In this case, when putting the parent entity only the relation is updated. This is because ObjectBox only puts related entities with an ID of 0. See the documentation about IDs for background information.

Example: Modelling Tree Relations

You can model a tree relation with a to-one and a to-many relation pointing to itself:

@Entity
public class TreeNode {
@Id long id;
ToOne<TreeNode> parent;
@BackLink
ToMany<TreeNode> children;
}

The generated entity lets you navigate its parent and children:

TreeNode parent = entity.parent.getTarget();
List<TreeNode> children = entity.children;