BRIDGING
JAVA OBJECTS AND RELATIONAL DATABASES
Author: Thomas Mahler, september 2001
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.
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; }
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.
$FOOTER$