Skip to content

Commit cdc58bd

Browse files
Merge branch 'relation-docs-updates' into 'dev'
Relation docs updates See merge request objectbox/objectbox-java!137
2 parents 4a6cb74 + 97e0584 commit cdc58bd

File tree

2 files changed

+149
-52
lines changed

2 files changed

+149
-52
lines changed

objectbox-java/src/main/java/io/objectbox/relation/ToMany.java

+87-36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 ObjectBox Ltd. All rights reserved.
2+
* Copyright 2017-2024 ObjectBox Ltd. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import io.objectbox.Cursor;
3737
import io.objectbox.InternalAccess;
3838
import io.objectbox.annotation.Backlink;
39+
import io.objectbox.annotation.Entity;
3940
import io.objectbox.annotation.apihint.Beta;
4041
import io.objectbox.annotation.apihint.Experimental;
4142
import io.objectbox.annotation.apihint.Internal;
@@ -51,23 +52,60 @@
5152
import static java.lang.Boolean.TRUE;
5253

5354
/**
54-
* A lazily loaded {@link List} of target objects representing a to-many relation, a unidirectional link from a "source"
55-
* entity to multiple objects of a "target" entity.
55+
* A to-many relation of an entity that references multiple objects of a {@link TARGET} entity.
5656
* <p>
57-
* It tracks changes (adds and removes) that can be later applied (persisted) to the database. This happens either when
58-
* the object that contains this relation is put or using {@link #applyChangesToDb()}. For some important details about
59-
* applying changes, see the notes about relations of {@link Box#put(Object)}.
57+
* Example:
58+
* <pre>{@code
59+
* // Java
60+
* @Entity
61+
* public class Student{
62+
* private ToMany<Teacher> teachers;
63+
* }
64+
*
65+
* // Kotlin
66+
* @Entity
67+
* data class Student() {
68+
* lateinit var teachers: ToMany<Teacher>
69+
* }
70+
* }</pre>
6071
* <p>
61-
* The objects are loaded lazily on first access of this list, and then cached. The database query runs on the calling
62-
* thread, so avoid accessing this from a UI or main thread. Subsequent calls to any method, like {@link #size()}, do
63-
* not query the database, even if the relation was changed elsewhere. To get the latest data {@link Box#get} the source
64-
* object again or use {@link #reset()} before accessing the list again.
72+
* Implements the {@link List} interface and uses lazy initialization. The target objects are only read from the
73+
* database when the list is first accessed.
6574
* <p>
75+
* The required database query runs on the calling thread, so avoid accessing ToMany from a UI or main thread. To get the
76+
* latest data {@link Box#get} the object with the ToMany again or use {@link #reset()} before accessing the list again.
6677
* It is possible to preload the list when running a query using {@link QueryBuilder#eager}.
6778
* <p>
79+
* Tracks when target objects are added and removed. Common usage:
80+
* <ul>
81+
* <li>{@link #add(Object)} to add target objects to the relation.
82+
* <li>{@link #remove(Object)} to remove target objects from the relation.
83+
* <li>{@link #remove(int)} to remove target objects at a specific index.
84+
* </ul>
85+
* <p>
86+
* To apply (persist) the changes to the database, call {@link #applyChangesToDb()} or put the object with the ToMany.
87+
* For important details, see the notes about relations of {@link Box#put(Object)}.
88+
* <p>
89+
* <pre>{@code
90+
* // Example 1: add target objects to a relation
91+
* student.getTeachers().add(teacher1);
92+
* student.getTeachers().add(teacher2);
93+
* store.boxFor(Student.class).put(student);
94+
*
95+
* // Example 2: remove a target object from the relation
96+
* student.getTeachers().remove(index);
97+
* student.getTeachers().applyChangesToDb();
98+
* // or store.boxFor(Student.class).put(student);
99+
* }</pre>
100+
* <p>
101+
* In the database, the target objects are referenced by their IDs, which are persisted as part of the relation of the
102+
* object with the ToMany.
103+
* <p>
68104
* ToMany is thread-safe by default (may not be the case if {@link #setListFactory(ListFactory)} is used).
105+
* <p>
106+
* To get all objects with a ToMany that reference a target object, see {@link Backlink}.
69107
*
70-
* @param <TARGET> Object type (entity).
108+
* @param <TARGET> target object type ({@link Entity @Entity} class).
71109
*/
72110
public class ToMany<TARGET> implements List<TARGET>, Serializable {
73111
private static final long serialVersionUID = 2367317778240689006L;
@@ -155,8 +193,8 @@ private void ensureBoxes() {
155193
try {
156194
boxStore = (BoxStore) boxStoreField.get(entity);
157195
if (boxStore == null) {
158-
throw new DbDetachedException("Cannot resolve relation for detached entities, " +
159-
"call box.attach(entity) beforehand.");
196+
throw new DbDetachedException("Cannot resolve relation for detached objects, " +
197+
"call box.attach(object) beforehand.");
160198
}
161199
} catch (IllegalAccessException e) {
162200
throw new RuntimeException(e);
@@ -226,9 +264,10 @@ private void ensureEntities() {
226264
}
227265

228266
/**
229-
* Adds the given entity to the list and tracks the addition so it can be later applied to the database
230-
* (e.g. via {@link Box#put(Object)} of the entity owning the ToMany, or via {@link #applyChangesToDb()}).
231-
* Note that the given entity will remain unchanged at this point (e.g. to-ones are not updated).
267+
* Prepares to add the given target object to this relation.
268+
* <p>
269+
* To apply changes, call {@link #applyChangesToDb()} or put the object with the ToMany. For important details, see
270+
* the notes about relations of {@link Box#put(Object)}.
232271
*/
233272
@Override
234273
public synchronized boolean add(TARGET object) {
@@ -329,8 +368,9 @@ public boolean containsAll(Collection<?> collection) {
329368
}
330369

331370
/**
332-
* @return An object for the given ID, or null if the object was already removed from its box
333-
* (and was not cached before).
371+
* Gets the target object at the given index.
372+
* <p>
373+
* {@link ToMany} uses lazy initialization, so on first access this will read the target objects from the database.
334374
*/
335375
@Override
336376
public TARGET get(int location) {
@@ -381,6 +421,9 @@ public ListIterator<TARGET> listIterator(int location) {
381421
return entities.listIterator(location);
382422
}
383423

424+
/**
425+
* Like {@link #remove(Object)}, but using the location of the target object.
426+
*/
384427
@Override
385428
public synchronized TARGET remove(int location) {
386429
ensureEntitiesWithTrackingLists();
@@ -389,6 +432,12 @@ public synchronized TARGET remove(int location) {
389432
return removed;
390433
}
391434

435+
/**
436+
* Prepares to remove the target object from this relation.
437+
* <p>
438+
* To apply changes, call {@link #applyChangesToDb()} or put the object with the ToMany. For important details, see
439+
* the notes about relations of {@link Box#put(Object)}.
440+
*/
392441
@SuppressWarnings("unchecked") // Cast to TARGET: If removed, must be of type TARGET.
393442
@Override
394443
public synchronized boolean remove(Object object) {
@@ -400,7 +449,9 @@ public synchronized boolean remove(Object object) {
400449
return removed;
401450
}
402451

403-
/** Removes an object by its entity ID. */
452+
/**
453+
* Like {@link #remove(Object)}, but using just the ID of the target object.
454+
*/
404455
public synchronized TARGET removeById(long id) {
405456
ensureEntities();
406457
int size = entities.size();
@@ -519,9 +570,9 @@ public int getRemoveCount() {
519570
}
520571

521572
/**
522-
* Sorts the list by the "natural" ObjectBox order for to-many list (by entity ID).
523-
* This will be the order when you get the entities fresh (e.g. initially or after calling {@link #reset()}).
524-
* Note that non persisted entities (ID is zero) will be put to the end as they are still to get an ID.
573+
* Sorts the list by the "natural" ObjectBox order for to-many list (by object ID).
574+
* This will be the order when you get the objects fresh (e.g. initially or after calling {@link #reset()}).
575+
* Note that non persisted objects (ID is zero) will be put to the end as they are still to get an ID.
525576
*/
526577
public void sortById() {
527578
ensureEntities();
@@ -550,7 +601,7 @@ else if (delta > 0)
550601
}
551602

552603
/**
553-
* Saves changes (added and removed entities) made to this relation to the database. For some important details, see
604+
* Saves changes (added and removed objects) made to this relation to the database. For some important details, see
554605
* the notes about relations of {@link Box#put(Object)}.
555606
* <p>
556607
* Note that this is called already when the object that contains this ToMany is put. However, if only this ToMany
@@ -563,12 +614,12 @@ public void applyChangesToDb() {
563614
long id = relationInfo.sourceInfo.getIdGetter().getId(entity);
564615
if (id == 0) {
565616
throw new IllegalStateException(
566-
"The source entity was not yet persisted (no ID), use box.put() on it instead");
617+
"The object with the ToMany was not yet persisted (no ID), use box.put() on it instead");
567618
}
568619
try {
569620
ensureBoxes();
570621
} catch (DbDetachedException e) {
571-
throw new IllegalStateException("The source entity was not yet persisted, use box.put() on it instead");
622+
throw new IllegalStateException("The object with the ToMany was not yet persisted, use box.put() on it instead");
572623
}
573624
if (internalCheckApplyToDbRequired()) {
574625
// We need a TX because we use two writers and both must use same TX (without: unchecked, SIGSEGV)
@@ -581,10 +632,10 @@ public void applyChangesToDb() {
581632
}
582633

583634
/**
584-
* Returns true if at least one of the entities matches the given filter.
635+
* Returns true if at least one of the target objects matches the given filter.
585636
* <p>
586637
* For use with {@link QueryBuilder#filter(QueryFilter)} inside a {@link QueryFilter} to check
587-
* to-many relation entities.
638+
* to-many relation objects.
588639
*/
589640
@Beta
590641
public boolean hasA(QueryFilter<TARGET> filter) {
@@ -599,10 +650,10 @@ public boolean hasA(QueryFilter<TARGET> filter) {
599650
}
600651

601652
/**
602-
* Returns true if all of the entities match the given filter. Returns false if the list is empty.
653+
* Returns true if all of the target objects match the given filter. Returns false if the list is empty.
603654
* <p>
604655
* For use with {@link QueryBuilder#filter(QueryFilter)} inside a {@link QueryFilter} to check
605-
* to-many relation entities.
656+
* to-many relation objects.
606657
*/
607658
@Beta
608659
public boolean hasAll(QueryFilter<TARGET> filter) {
@@ -619,7 +670,7 @@ public boolean hasAll(QueryFilter<TARGET> filter) {
619670
return true;
620671
}
621672

622-
/** Gets an object by its entity ID. */
673+
/** Gets an object by its ID. */
623674
@Beta
624675
public TARGET getById(long id) {
625676
ensureEntities();
@@ -634,7 +685,7 @@ public TARGET getById(long id) {
634685
return null;
635686
}
636687

637-
/** Gets the index of the object with the given entity ID. */
688+
/** Gets the index of the object with the given ID. */
638689
@Beta
639690
public int indexOfId(long id) {
640691
ensureEntities();
@@ -653,7 +704,7 @@ public int indexOfId(long id) {
653704

654705
/**
655706
* Returns true if there are pending changes for the DB.
656-
* Changes will be automatically persisted once the owning entity is put, or an explicit call to
707+
* Changes will be automatically persisted once the object with the ToMany is put, or an explicit call to
657708
* {@link #applyChangesToDb()} is made.
658709
*/
659710
public boolean hasPendingDbChanges() {
@@ -668,7 +719,7 @@ public boolean hasPendingDbChanges() {
668719

669720
/**
670721
* For internal use only; do not use in your app.
671-
* Called after relation source entity is put (so we have its ID).
722+
* Called after relation source object is put (so we have its ID).
672723
* Prepares data for {@link #internalApplyToDb(Cursor, Cursor)}
673724
*/
674725
@Internal
@@ -692,7 +743,7 @@ public boolean internalCheckApplyToDbRequired() {
692743
// Relation based on Backlink
693744
long entityId = relationInfo.sourceInfo.getIdGetter().getId(entity);
694745
if (entityId == 0) {
695-
throw new IllegalStateException("Source entity has no ID (should have been put before)");
746+
throw new IllegalStateException("Object with the ToMany has no ID (should have been put before)");
696747
}
697748
IdGetter<TARGET> idGetter = relationInfo.targetInfo.getIdGetter();
698749
Map<TARGET, Boolean> setAdded = this.entitiesAdded;
@@ -866,7 +917,7 @@ public void internalApplyToDb(Cursor<?> sourceCursor, Cursor<TARGET> targetCurso
866917
if (isStandaloneRelation) {
867918
long entityId = relationInfo.sourceInfo.getIdGetter().getId(entity);
868919
if (entityId == 0) {
869-
throw new IllegalStateException("Source entity has no ID (should have been put before)");
920+
throw new IllegalStateException("Object with the ToMany has no ID (should have been put before)");
870921
}
871922

872923
if (removedStandalone != null) {
@@ -909,7 +960,7 @@ private void addStandaloneRelations(Cursor<?> cursor, long sourceEntityId, TARGE
909960
long targetId = targetIdGetter.getId(added[i]);
910961
if (targetId == 0) {
911962
// Paranoia
912-
throw new IllegalStateException("Target entity has no ID (should have been put before)");
963+
throw new IllegalStateException("Target object has no ID (should have been put before)");
913964
}
914965
targetIds[i] = targetId;
915966
}

0 commit comments

Comments
 (0)