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.
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@Entitypublic class Customer {@Id public long id;}​// Order.java@Entitypublic class Order {@Id public long id;public ToOne<Customer> customer;}
@Entitydata class Customer(@Id var id: Long = 0)​@Entitydata class Order(@Id var id: Long = 0) {lateinit var customer: ToOne<Customer>}
Now let’s add a customer and some orders. To set the related customer object, call setTarget()
(or assign target
in Kotlin) on the ToOne instance and put the order object:
Customer customer = new Customer();Order order = new Order();order.customer.setTarget(customer);// Puts order and customer:long orderId = boxStore.boxFor(Order.class).put(order);
val customer = Customer()val order = Order()order.customer.target = customer// Puts order and customer:val orderId = boxStore.boxFor(Order::class.java).put(order)
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()
(or access target
in Kotlin) on the ToOne instance:
Order order = boxStore.boxFor(Order.class).get(orderId);Customer customer = order.customer.getTarget();
val order = boxStore.boxFor(Order::class.java)[orderId]val customer = order.customer.target
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);
order.customer.target = nullboxStore.boxFor(Order::class.java).put(order)
Note that this does not remove the customer from the database, it just dissolves the relationship.
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.
You can directly access the target ID property by defining a long
(or Long
in Kotlin) property in your entity class with the expected name:
@Entitypublic class Order {@Id public long id;public long customerId; // ToOne target ID propertypublic ToOne<Customer> customer;}
@Entitydata class Order(@Id var id: Long = 0,var customerId: Long = 0) {lateinit var customer: ToOne<Customer>}
You can change the name of the expected target ID property by adding the @TargetIdProperty(String) annotation to a ToOne.
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:
@Entitypublic class Example {​ToOne<Order> order;ToMany<Order> orders;transient BoxStore __boxStore; // <-- Transform adds this fieldpublic Example() {// Transform inits ToOne and ToMany in constructors// not calling another constructorthis.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.
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:
@Entitypublic 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 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 first access, and then cached in the source entity inside the ToMany
object. So subsequent calls to any method, like size()
of the ToMany
, do not query the database, even if the relation was changed elsewhere. To get the latest data fetch the source entity again or call reset()
on the ToMany
.
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.
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@Entitypublic class Customer {@Id public long id;// 'to' is optional if only one relation matches.@Backlink(to = "customer")public ToMany<Order> orders;}​// Order.java@Entitypublic class Order {@Id public long id;public ToOne<Customer> customer;}
@Entitydata class Customer(@Id var id: Long = 0) {// 'to' is optional if only one relation matches.@Backlink(to = "customer")lateinit var orders: ToMany<Order>}​@Entitydata class Order(@Id var id: Long = 0) {lateinit var customer: ToOne<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("Order 1"));customer.orders.add(new Order("Order 2"));// Puts customer and orders:long customerId = boxStore.boxFor(Customer.class).put(customer);
val customer = Customer()customer.orders.add(Order(text = "Order 1"))customer.orders.add(Order(text = "Order 2"))// Puts customer and orders:val customerId = boxStore.boxFor(Customer::class.java).put(customer)
For Kotlin: two data classes that have the same property values (excluding those defined in the class body) are equal and have the same hash code. Keep this in mind when working with ToMany which uses a HashMap to keep track of changes. E.g. adding the same data class multiple times has no effect, it is treated as the same entity.
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) {// Do something with each order.}
val customer = boxStore.boxFor(Customer::class.java).get(customerId)for (order in customer.orders) {// Do something with each order.}
Removing orders from the relation works as expected:
// Remove the relation to an order:Order order = customer.orders.remove(0);boxStore.boxFor(Customer.class).put(customer);// Optional: also remove the order entity from its box:// boxStore.boxFor(Order.class).remove(order);
// Remove the relation to an order:val order = customer.orders.removeAt(0)boxStore.boxFor(Customer::class.java).put(customer)// Optional: also remove the order entity from its box:// boxStore.boxFor(Order::class.java).remove(order)
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@Entitypublic class Teacher{@Id public long id;}​// Student.java@Entitypublic class Student{@Id public long id;public ToMany<Teacher> teachers;}
@Entitydata class Teacher(@Id var id: Long = 0)​@Entitydata class Student(@Id var id: Long = 0) {lateinit var teachers: ToMany<Teacher>}
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);
val teacher1 = Teacher()val teacher2 = Teacher()​val student1 = Student()student1.teachers.add(teacher1)student1.teachers.add(teacher2)​val student2 = Student()student2.teachers.add(teacher2)​// Puts students and teachers:boxStore.boxFor(Student::class.java).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 student = boxStore.boxFor(Student.class).get(studentId);for (Teacher teacher : student.teachers) {// Do something with each teacher.}
val student = boxStore.boxFor(Student::class.java).get(studentId)for (teacher in student.teachers) {// Do something with each teacher.}
And if a student drops out of a class, we can remove a teacher from the relation:
student.teachers.remove(0);// Simply put the student again:// boxStore.boxFor(Student.class).put(student);// Or more efficient than using put:student.teachers.applyChangesToDb();
student.teachers.removeAt(0)// Simply put the student again:// boxStore.boxFor(Student::class.java).put(student)// Or more efficient than using put:student.teachers.applyChangesToDb()
Note: instead of using put()
you can also use applyChangesToDb()
of the ToMany
to persist changes that affect it only.
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@Entitypublic class Teacher{@Id public long id;// Backed by the to-many relation in Student:@Backlink(to = "teachers")public ToMany<Student> students;}​// Student.java@Entitypublic class Student{@Id public long id;public ToMany<Teacher> teachers;}
@Entitydata class Teacher(@Id var id: Long = 0) {// Backed by the to-many relation in Student:@Backlink(to = "teachers")lateinit var students: ToMany<Student>}​@Entitydata class Student(@Id var id: Long = 0) {lateinit var teachers: ToMany<Teacher>}
The ToOne and ToMany relation classes assist you to persist the relation state. They keep track of changes and apply them to the database once you put the Object containing them. ObjectBox supports relation updates for new (not yet persisted; ID == 0) and existing (persisted before; ID != 0) Objects.
For convenience, ToOne and ToMany will put Objects added to them 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 (internally the Object ID is added to or removed from the relation). In that case, to put changes to the properties of related Objects use their specific Box instead:
// update a related entity using its boxOrder orderToUpdate = customer.orders.get(0);orderToUpdate.text = "Revised description";// DOES NOT WORK// boxStore.boxFor(Customer.class).put(customer);// WORKSboxStore.boxFor(Order.class).put(orderToUpdate);
The ToOne class offers the following methods to update the relation:
setTarget(target)
makes the given (new or existing) Object the new relation target; pass null
to clear the relation.
setTargetId(targetId)
sets the relation target based on the given ID of an existing Object; pass 0
(zero) to clear the relation.
setAndPutTarget(target)
makes the given (new or existing) Object the new relation target and puts the owning, source Object and if needed the target Object.
// Option 1: set target and put.order.customer.setTarget(customer);// Or set target via Object ID:// order.customer.setCustomerId(customer.getId());orderBox.put(order);​// Option 2: combined set target and put.order.customer.setAndPutTarget(customer);
Note: attach the Box before calling setAndPutTarget()
on a new (not put) Object owning a ToOne:
// Attach box before using setAndPutTarget on new ObjectOrder order = new Order();orderBox.attach(order);order.customer.setAndPutTarget(customer);
Note: if the target Object uses self-assigned IDs with @Id(assignable = true)
it will not be put when the Object that owns the relation is put. Read on for details:
If the target Object uses self-assigned IDs, put it before setting it as a target of the ToOne relation:
Customer customer = new Customer();// If ID is self-assigned, put target Object firstcustomer.id = 12;customerBox.put(customer);// Then can safely set as targetorder.customer.setTarget(customer);// Or set target via Object ID// 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.
The ToMany relation class is a java.lang.List
plus added change tracking for Objects. As mentioned above, it will put new Objects (ID == 0) that are added to it once the Object owning it is put. And when removing Objects from it, just the relation is cleared, the Objects are not removed from their Box.
See the documentation on One-to-Many and Many-to-Many above for details.
Note: if your entities are using self-assigned IDs with @Id(assignable = true)
additional steps are required. Read on for details:
If the owning, source Object uses @Id(assignable = true)
attach its Box before modifying its ToMany:
// If source has self-assigned ID attach Box firstcustomer.id = 12;customerBox.attach(customer);// Then can safely modify ToManycustomer.orders.add(order);customerBox.put(customer);
If the target Object, like Order
above, is using self-assigned IDs put the target Objects before adding them to the ToMany relation:
// If ID is self-assigned put target Object firstorder.id = 42;orderBox.put(order);// Then can safely add target Object to ToManycustomer.orders.add(order);customerBox.put(customer);
The above steps are required because, when putting the Object owning the ToMany only the relation is updated. This is because ObjectBox only puts target Objects with an ID of 0. See the documentation about IDs for background information.
A typical extension to the customer/order example we started earlier, would be to add an Address
type. While you could model street, ZIP and so on directly into Customer
, it usually makes sense to normalize that out into a separate entity. And when you think about the usual shopping sites, they all let you have multiple addresses...
So, typically, this is how you would model that:
an address is bound to a customer; add a ToOne<Customer> customer
to Address
an order is shipped to one address, and might have a diverging billing address optionally; add ToOne<Address> shippingAddress
and ToOne<Address> billingAddress
to Order
For each ToOne
you could have a matching ToMany
on the other side of the relation (backlink)
You can model a tree relation with a to-one and a to-many relation pointing to itself:
@Entitypublic class TreeNode {@Id long id;ToOne<TreeNode> parent;@BackLinkToMany<TreeNode> children;}
@Entitydata class TreeNode(@Id var id: Long = 0) {lateinit var parent: ToOne<TreeNode>@Backlinklateinit var children: ToMany<TreeNode>}
This lets you navigate a tree nodes parent and children:
TreeNode parent = treeNode.parent.getTarget();List<TreeNode> children = treeNode.children;
val parent: TreeNode = treeNode.parent.targetval children: List<TreeNode> = treeNode.children