JDO : Persistence Manager

As you read in the guide for PersistenceManagerFactory, to control the persistence of your objects you will require at least one PersistenceManagerFactory. Once you have obtained this object you then use this to obtain a PersistenceManager (PM). A PersistenceManager provides access to the operations for persistence of your objects. This short guide will demonstrate some of the more common operations. For example with a web application you would have one PMF representing the datastore, present for the duration of the application, and then have a PM per request that comes in, closing it before responding.

Important : A PersistenceManagerFactory is designed to be thread-safe. A PersistenceManager is not

You obtain a PersistenceManager as follows

PersistenceManager pm = pmf.getPersistenceManager();

You likely will be performing all operations on a PersistenceManager within a transaction, whether your transactions are controlled by your JavaEE container, by a framework such as Spring, or by locally defined transactions. Alternatively you can perform your operations non-transactional. In the examples below we will omit the transaction demarcation for clarity.


Persisting an Object

The main thing that you will want to do with the data layer of a JDO-enabled application is persist your objects into the datastore. As we mentioned earlier, a PersistenceManagerFactory represents the datastore where the objects will be persisted. So you create a normal Java object in your application, and you then persist this as follows

pm.makePersistent(obj);

This will result in the object being persisted into the datastore, though clearly it will not be persistent until you commit the transaction. The LifecycleState of the object changes from Transient to PersistentClean (after makePersistent), to Hollow (at commit).


Finding an object by its identity

Once you have persisted an object, it has an "identity". This is a unique way of identifying it. You can obtain the identity by calling

Object id = pm.getObjectId(obj);

Alternatively by calling

Object id = pm.newObjectIdInstance(cls, key);

So what ? Well the identity can be used to retrieve the object again at some other part in your application. So you pass the identity into your application, and the user clicks on some button on a web page and that button corresponds to a particular object identity. You can then go back to your data layer and retrieve the object as follows

Object obj = pm.getObjectById(id);

A DataNucleus extension is to pass in a String form of the identity to the above method. It accepts identity strings of the form

  • {fully-qualified-class-name}:{key}
  • {discriminator-name}:{key}

where the key is the identity value (datastore-identity) or the result of PK.toString() (application-identity). So for example we could input

obj = pm.getObjectById("mydomain.MyClass:3");

There is, of course, a bulk load variant too

Object[] objs = pm.getObjectsById(ids);

When you call the method getObjectById if an object with that identity is found in the cache then a call is, by default, made to validate it still exists. You can avoid this call to the datastore by setting the persistence property datanucleus.findObject.validateWhenCached to false.

Finding an object by its class and primary-key value

An alternate form of the getObjectById method is taking in the class of the object, and the "identity". This is for use where you have a single field that is primary key. Like this

Object id = pm.getObjectId(MyClass.class, 123);

where 123 is the value of the primary key field (numeric). Note that the first argument could be a base class and the real object could be an instance of a subclass of that.


Deleting an Object

When you need to delete an object that you had previous persisted, deleting it is simple. Firstly you need to get the object itself, and then delete it as follows

Object obj = pm.getObjectById(id);  // Retrieves the object to delete
pm.deletePersistent(obj);

Don't forget that you can also use deletion by query to delete objects. Alternatively use bulk deletion.


Modifying a persisted Object

To modify a previously persisted object you need to retrieve it (getObjectById, query, getExtent) and then modify it and its changes will be propagated to the datastore at commit of the transaction.

Don't forget that you can also use bulk update to update a group of objects of a type.


Detaching a persisted Object

You often have a previously persisted object and you want to use it away from the data-access layer of your application. In this case you want to detach the object (and its related objects) so that they can be passed across to the part of the application that requires it. To do this you do

Object detachedObj = pm.detachCopy(obj); // Returns a copy of the persisted object, in detached state

The detached object is like the original object except that it has no StateManager connected, and it stores its JDO identity and version. It retains a list of all fields that are modified while it is detached. This means that when you want to "attach" it to the data-access layer it knows what to update.

As an alternative, to make the detachment process transparent, you can set the PMF property datanucleus.DetachAllOnCommit to true and when you commit your transaction all objects enlisted in the transaction will be detached.


Attaching a persisted Object

You've detached an object (shown above), and have modified it in your application, and you now want to attach it back to the persistence layer. You do this as follows

Object attachedObj = pm.makePersistent(obj); // Returns a copy of the detached object, in attached state

Refresh of objects

In the situation where you have an object and you think that its values may have changed in the datastore you can update its values to the latest using the following

pm.refresh(obj);

What this will do is as follows

  • Refresh the values of all FetchPlan fields in the object
  • Unload all non-FetchPlan fields in the object

If the object had any changes they will be thrown away by this step, and replaced by the latest datastore values.


Level 1 Cache

Each PersistenceManager maintains a cache of the objects that it has encountered (or have been "enlisted") during its lifetime. This is termed the Level 1 Cache. It is enabled by default and you should only ever disable it if you really know what you are doing. There are inbuilt types for the Level 1 (L1) Cache available for selection. DataNucleus supports the following types of L1 Cache :-

  • weak - uses a weak reference backing map. If JVM garbage collection clears the reference, then the object is removed from the cache.
  • soft - uses a soft reference backing map. If the map entry value object is not being actively used, then garbage collection may garbage collect the reference, in which case the object is removed from the cache.
  • strong - uses a normal HashMap backing. With this option all references are strong meaning that objects stay in the cache until they are explicitly removed by calling remove() on the cache.
  • none - will turn off L1 caching. Only ever use this where the cache is of no use and you are performing bulk operations and not requiring objects returned

You can specify the type of L1 cache by providing the persistence property datanucleus.cache.level1.type. You set this to the value of the type required. If you want to remove objects from the L1 cache programmatically you should use the pm.evict or pm.evictAll methods.

Objects are placed in the L1 cache (and updated there) during the course of the transaction. This provides rapid access to the objects in use in the users application and is used to guarantee that there is only one object with a particular identity at any one time for that PersistenceManager. When the PersistenceManager is closed the L1 cache is cleared.

The L1 cache is a DataNucleus plugin point allowing you to provide your own cache where you require it.