JDO : Cascading Operations

When defining your objects to be persisted and the relationships between them, it is often required to define dependencies between these related objects. What should happen when persisting an object and it relates to another object? What should happen to a related object when an object is deleted? You can define what happens with JDO and with DataNucleus. Let's take an example

public class Owner
{
    private DrivingLicense license;
    private Collection cars;
    
    ...
}

public class DrivingLicense
{
    private String serialNumber;
    
    ...
}

public class Car
{
    private String registrationNumber;
    private Owner owner;
    
    ...
}

So we have an Owner of a collection of vintage Car's (1-N), and the Owner has a DrivingLicense (1-1). We want to define lifecycle dependencies to match the relationships that we have between these objects. Firstly lets look at the basic Meta-Data for the objects.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo SYSTEM "file:/javax/jdo/jdo.dtd">
<jdo>
    <package name="com.mydomain.samples.cars">
        <class name="Owner">
            <field name="license" persistence-modifier="persistent"/>
            <field name="cars">
                <collection element-type="com.mydomain.samples.cars.Car" mapped-by="owner"/>
            </field>
        </class>
        
        <class name="DrivingLicense">
        	<field name="serialNumber"/>
        </class>
        
        <class name="Car">
            <field name="registrationNumber"/>
            <field name="owner" persistence-modifier="persistent"/>
        </class>
    </package>
</jdo>

Persistence

JDO defines a concept called persistence-by-reachability. This means that when you persist an object and it has a related persistable object then this other object is also persisted. So using our example if we do

Owner bob = new Owner("Bob Smith");
DrivingLicense license = new DrivingLicense("011234BX4J");
bob.setLicense(license);
pm.makePersistent(bob); // "bob" knows about "license"

This results in both the Owner and the DrivingLicense objects being made persistent since the Owner is passed to the PM operation and it has a field referring to the unpersisted DrivingLicense object. So "reachability" will persist the license.


extension

With DataNucleus you can actually turn off persistence-by-reachability for particular fields, by specifying in the MetaData a DataNucleus extension tag, as follows

<class name="Owner">
    <field name="license" persistence-modifier="persistent">
        <extension vendor-name="datanucleus" key="cascade-persist" value="false"/>
    </field>
    ...
</class>

So with this specification when we call makePersistent() with an object of type Owner then the field "license" will not be persisted at that time.


Update

As mentioned above JDO defines a concept called persistence-by-reachability. This applies not just to persist but also to update of objects, so when you update an object and its updated field has a persistable object then that will be persisted. So using our example if we do

Owner bob = (Owner)pm.getObjectById(id);
DrivingLicense license2 = new DrivingLicense("233424BX4J");
bob.setLicense(license2); // "bob" knows about "license2"

So when this field is updated the new DrivingLicense object will be made persistent since it is reachable from the persistent Owner object.


extension

With DataNucleus you can actually turn off update-by-reachability for particular fields, by specifying in the MetaData a DataNucleus extension tag, as follows

<class name="Owner">
    <field name="license" persistence-modifier="persistent">
        <extension vendor-name="datanucleus" key="cascade-update" value="false"/>
    </field>
    ...
</class>

So with this specification when we call makePersistent() to update an object of type Owner then the field "license" will not be updated at that time.


Deletion, using Dependent Field

So we have an inverse 1-N relationship (no join table) between our Owner and his precious Car's, and a 1-1 relationship between the Owner and his DrivingLicense, because without his license he wouldn't be able to drive the cars :-0. What will happen to the license and the cars when the owner dies ? Well in this particular case we want to define that the when the owner is deleted, then his license will also be deleted (since it is for him only), but that his cars will continue to exist, because his daughter will inherit them. In JDO this is called Dependent Fields. To utilise this concept to achieve our end goal we change the Meta-Data to be

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo SYSTEM "file:/javax/jdo/jdo.dtd">
<jdo>
    <package name="com.mydomain.samples.cars">
        <class name="Owner">
            <field name="license" persistence-modifier="persistent" dependent="true"/>
            <field name="cars">
                <collection element-type="com.mydomain.samples.cars.Car" mapped-by="owner" 
                    dependent-element="false"/>
            </field>
        </class>
        
        <class name="DrivingLicense">
            <field name="serialNumber"/>
        </class>
        
        <class name="Car">
            <field name="registrationNumber"/>
            <field name="owner" persistence-modifier="persistent" dependent="false"/>
        </class>
    </package>
</jdo>

So it was as simple as just adding dependent and dependent-element attributes to our related fields. Notice that we also added one to the other end of the Owner-Car relationship, so that when a Car comes to the end of its life, the Owner will not die with it. It may be the case that the owner dies driving the car and they both die at the same time, but their deaths are independent!!

Just as we made use of dependent-element for collection fields, we also can make use of dependent-key and dependent-value for map fields, and dependent-element for array fields.

Dependent Fields is utilised in the following situations

  • An object is deleted (using deletePersistent()) and that object has relations to other objects. If the other objects (either 1-1, 1-N, or M-N) are dependent then they are also deleted.
  • An object has a 1-1 relation with another object, but the other object relation is nulled out. If the other object is dependent then it is deleted when the relation is nulled.
  • An object has a 1-N collection relation with other objects and the element is removed from the collection. If the element is dependent then it will be deleted when removed from the collection. The same happens when the collections is cleared.
  • An object has a 1-N map relation with other objects and the key is removed from the map. If the key or value are dependent and they are not present in the map more than once they will be deleted when they are removed. The same happens when the map is cleared.

Deletion, using Foreign Keys (RDBMS)

With JDO2 you can use "dependent-field" as shown above. As an alternative, when using RDBMS, you can use the datastore-defined foreign keys and let the datastore built-in "referential integrity" look after such deletions. DataNucleus provides a PMF property datanucleus.deletionPolicy allowing enabling of this mode of operation.

The default setting of datanucleus.deletionPolicy is "JDO2" which performs deletion of related objects as follows

  1. If dependent-field is true then use that to define the related objects to be deleted.
  2. Else, if the column of the foreign-key field is NULLable then NULL it and leave the related object alone
  3. Else deleted the related object (and throw exceptions if this fails for whatever datastore-related reason)

The other setting of datanucleus.deletionPolicy is "DataNucleus" which performs deletion of related objects as follows

  1. If dependent-field is true then use that to define the related objects to be deleted.
  2. If a foreign-key is specified (in MetaData) for the relation field then leave any deletion to the datastore to perform (or throw exceptions as necessary)
  3. Else, if the column of the foreign-key field is NULLable then NULL it and leave the related object alone
  4. Else deleted the related object (and throw exceptions if this fails for whatever datastore-related reason)

So, as you can see, with the second option you have the ability to utilise datastore "referential integrity" checking using your MetaData-specified <foreign-key> elements.