why a cache
OJB provides a simple Object Cache that
holds Objects previously loaded or stored by the PersistenceBroker.
Using a Cache has several advantages:
- it increases performance as it reduces database
lookups. If an object is looked up by the
PersistenceBroker, it does not perform a SELECT against the database
immediately but first looks up the
cache if the requested object is already loaded. If the object is
cached it is returned as the lookup result. If it is not cached a
SELECT is performed.
- it maintains the uniqueness of objects. If several queries
ask for an object with the same Identity (OID) they will receive one
and the same object from the cache. This will prevent your
applications from working with divergent copies of objects with the
same Identity.
- it allows to perform circular lookups (as by crossreferenced
objects) that would result in non-terminating loops without such a
cache.
how it works
The OJB Cache provides the following interface to allow caching,
lookup and removal of objects:
package ojb.broker.accesslayer;
public interface ObjectCache
{
/**
* Make object obj persistent to Objectcache.
* compute objects identity and use it as key for the hashmap
*/
public void cache(Object obj) throws ClassNotPersistenceCapableException;
/**
* makes object obj persistent to the Objectcache under the key oid.
*/
public void cache(Identity oid, Object obj);
/**
* Lookup object with Identity oid in objectTable.
* returns null if no matching id is found
*/
public Object lookup(Identity oid);
/**
* removes an Object from the cache.
*/
public void remove(Object obj);
/**
* clear the ObjectCache.
*/
public void clear();
}
The OJB default implementation resides in the class
ojb.broker.accesslayer.ObjectCacheDefaultImpl. This implementation
uses a hashtable to hold all cached objects:
private Hashtable objectTable = new Hashtable();
The Hashtable uses Identities as keys and object-references as
values. For an example see the code of the .cache(...)
method:
public void cache(Identity oid, Object obj)
{
if (obj != null)
{
SoftReference ref = new SoftReference(obj);
objectTable.put(oid.toString(), ref);
}
}
As you will have noticed, OJB uses SoftReferences. A SoftReference is
not treated as a ordinary java reference. That is: if an object is
not longer referenced by your application but only by the cache it
can be reclaimed by the garbage collector (gc). The OJB cache does
not provide its own memory management but lets the JVM gc do the job.
Thus the lookup method has to take care if the gc did reclaim
objects:
public Object lookup(Identity oid)
{
Object obj = null;
SoftReference ref = (SoftReference) objectTable.get(oid.toString());
if (ref != null)
{
obj = ref.get();
if (obj == null) // Soft-referenced Object reclaimed by GC !
{
objectTable.remove(oid);
}
}
return obj;
}
implement your own cache
The OJB cache is quite simple but does a good job for most
scenarios. If you need a more sophisticated cache (e.g. with MRU
memory management strategies) you'll write your own implementation of
the interface ojb.broker.accesslayer.ObjectCache.
OJB provides a simple mechanism to integrate your implementation:
OJB obtains its cache instance from the factory
ojb.broker.accesslayer.ObjectCacheFactory.
This Factory can be configured to generate instances of your specific
implementation by changing the following entry in the configuration
file OJB.properties:
ObjectCacheClass=ojb.broker.accesslayer.ObjectCacheDefaultImpl
to:
ObjectCacheClass=acme.com.MyOwnObjectCache.
Of course I'm interested in your solutions! If you have
implemented something interesting, just contact me.