JDO : Managing Relationships

The power of a Java persistence solution like DataNucleus is demonstrated when persisting relationships between objects. There are many types of relationships.

  • 1-1 relationships - this is where you have an object A relates to a second object B. The relation can be unidirectional where A knows about B, but B doesnt know about A. The relation can be bidirectional where A knows about B and B knows about A.
  • 1-N relationships - this is where you have an object A that has a collection of other objects of type B. The relation can be unidirectional where A knows about the objects B but the Bs dont know about A. The relation can be bidirectional where A knows about the objects B and the Bs know about A
  • N-1 relationships - this is where you have an object B1 that relates to an object A, and an object B2 that relates to A also etc. The relation can be unidirectional where the A doesnt know about the Bs. The relation can be bidirectional where the A has a collection of the Bs. [i.e a 1-N relationship but from the point of view of the element]
  • M-N relationships - this is where you have objects of type A that have a collection of objects of type B and the objects of type B also have a collection of objects of type A. The relation is always bidirectional by definition
  • Compound Identity relationships when you have a relation and part of the primary key of the related object is the other persistent object.

Assigning Relationships

When the relation is unidirectional you simply set the related field to refer to the other object. For example we have classes A and B and the class A has a field of type B. So we set it like this

A a = new A();
B b = new B();
a.setB(b); // "a" knows about "b"

When the relation is bidirectional you have to set both sides of the relation. For example, we have classes A and B and the class A has a collection of elements of type B, and B has a field of type A. So we set it like this

A a = new A();
B b1 = new B();
a.addElement(b1); // "a" knows about "b1"
b1.setA(a); // "b1" knows about "a"

So it is really simple, with only 1 general rule. With a bidirectional relation you should set both sides of the relation


Reachability

With JDO, when you persist an object, all related objects (reachable from the fields of the object being persisted) will be persisted at the same time (unless already persistent). This is called persistence-by-reachability. For example

A a = new A();
B b = new B();
a.setB(b);
pm.makePersistent(a); // "a" and "b" are now provisionally persistent

This additionally applies when you have an object managed by the PersistenceManager, and you set a field to refer to a related object - this will make the related object provisionally persistent also. For example

A a = new A();
pm.makePersistent(a); // "a" is now provisionally persistent
B b = new B();
a.setB(b); // "b" is now provisionally persistent

Persistence-By-Reachability-At-Commit : One additional feature of JDO is the ability to re-run the persistence-by-reachability algorithm at commit so as to check whether the objects being made persistent should definitely be persisted. This is for the following situation.

  • Start a transaction
  • Persist object A. This persists related object B.
  • Delete object A from persistence
  • Commit the transaction.
If you have property datanucleus.persistenceByReachabilityAtCommit set to true (default) then this will recheck the persisted objects should remain persistent. In this case it will find B and realise that it was only persisted due to A (which has since been deleted), hence B will not remain persistent after the transaction.
If you had property datanucleus.persistenceByReachabilityAtCommit set to false then B will remain persistent after the transaction.

Managed Relationships

As previously mentioned, users should really set both sides of a bidirectional relation. DataNucleus provides a good level of managed relations in that it will attempt to correct any missing information in relations to make both sides consistent. What it provides is defined below

For a 1-1 bidirectional relation, at persist you should set one side of the relation and the other side will be set to make it consistent. If the respective sides are set to inconsistent objects then an exception will be thrown at persist. At update of owner/non-owner side the other side will also be updated to make them consistent.

For a 1-N bidirectional relation and you only specify the element owner then the collection must be Set-based since DataNucleus cannot generate indexing information for you in that situation (you must position the elements). At update of element or owner the other side will also be updated to make them consistent. At delete of element the owner collection will also be updated to make them consistent. If you are using a List you MUST set both sides of the relation

For an M-N bidirectional relation, at persist you MUST set one side and the other side will be populated at commit/flush to make them consistent.

This management of relations can be turned on/off using a persistence property datanucleus.manageRelationships. If you always set both sides of a relation at persist/update then you could safely turn it off.

When performing management of relations there are some checks implemented to spot typical errors in user operations e.g add an element to a collection and then remove it (why?!). You can disable these checks using datanucleus.manageRelationshipsChecks, set to false.