JCS Usage in Hibernate
OverviewJCS is a caching technology built in to Hibernate. The JCS project site can be found at http://jakarta.apache.org/turbine/jcs/ . JCS can be used to store commonly accessed domain objects, avoiding expensive database access. This document is a supplement to the existing Hibernate documentation, looking at loading strategies and their impact on cacheing.
ExamplesExamples given in this document refer to a Country domain object. Country has a many-to-one association with Region and a many-to-many association with CreditCard. Region has a many-to-one association with itself, representing a parent region. Test data used:
- Country 239
- Region 1500
- CreditCard 7
Retrieving the Country object Australia will create:
- 1 Country Object
- 5 Credit Card Objects
- 3 Region objects, i.e. Australia is the grandchild of the root Region object.
Cached objects may be read-write or read-only. All examples use the read-only syntax.
Performance TimingsPerformance timings were produced in the following environment: Application Server
- Pentium IV 1.6Ghz 512MB RAM
- Orion 1.6
- Linux 2.4
Database Server
- Pentium IV 2Ghz 1024MB RAM
- Oracle 9i Release 2
- Linux 2.4
The first time a cacheable query is done, the cache has no effect on speed. On the second and successive queries, the cache will be populated and available to be hit. Timings are given for the LRU memory cache with no timeout.
<hibernatedoclet ... />hibernatedoclet, a module of the xdoclet project (see http://xdoclet.sourceforge.net) can be used to automate the production of Hibernate mapping files. In hibernatedoclet, jcs cache tags can be inserted at the class level, in which case the object is cacheable, or at the method level for the following Hibernate types: set, list, map, bag, array and primitive-array. See http://hibernate.sourceforge.net/hibernate-mapping.dtd for more information. class level
/*
* @hibernate.class table="COUNTRY"
* @hibernate.jcs-cache usage="read-only"
*/
public class Country { ...
The class level tag will make the object cacheable, but not collections representing many to many relationships. method level
/**
* @hibernate.set role="creditCards" table="COUNTRY_CREDIT_CARD"
* @hibernate.jcs-cache usage="read-only"
* @hibernate.collection-key column="FK_COUNTRY"
* @hibernate.collection-many-to-many class="com.gregluck.domain.CreditCard" column="FK_CREDIT_CARD"
*/
public Set getCreditCards() {
return creditCards;
}
The method level tag applied to a set, list, map, bag, array or primitive-array will make the collection cacheable. If the jcs-cache tag is added to all collection types in an object, then the entire object is cacheable. (As of xdoclet 1.2 beta2, the method level jcs-cache tag is not implemented. You should work from XDoclet CVS for now. The patch should make it into xdoclet 1.2 beta 3.)
Object Loading MethodsThe following discussion assumes that
<jcs-cache usage="read-only" />
tags are set at the class and method level in the Hibernate mapping files for all classes. In our example this is Country, Region and CreditCard. If Region does not have jcs-cache set, its Region member variable will never be cached.
Session.load()Session.load(...) will attempt to load the object and any member variables from cache. In our Country example, the object graph is 100% cached. Example
country = (Country) session.load(Country.class, id);
Performance
- Loading the object graph takes an average of 21ms with cacheing on
- Loading the object graph takes an average of 56ms with cacheing off
Strategies for Use Session.load is the fastest way of loading an object where the id is known.
Session.find()Session.find(...) uses Hibernate HQL to execute a query. It is translated into SQL and executed against the database. The cache is never utilised by find(), until Hibernate tries to resolve associations after the initial query. (Of course, find() does add objects to the cache.) Example
Long id = new Long(5);
List countries = session.find(
"from country in class com.gregluck.domain.Country where country.id > ? ",
id,
Hibernate.LONG);
This example will load all countries with an id greater than 5. Performance
- Loading 239 countries takes an average of 137ms with cacheing on or off.
Strategies for Use Session.find() cannot efficiently take advantage of the cache. However, it can be used to efficiently load objects into the cache.
Session.iterate()Session.iterate(...) uses Hibernate HQL to execute a query which returns object ids only. As objects are iterated over they are loaded using load() transparently. The result of the first query (which returns ids) is never cached. The iteration is cached. Example
Collection countries = new ArrayList();
session = sessionFactory.openSession();
Iterator i = session.iterate(
"from country in class com.gregluck.domain.Country");
while (i.hasNext()) {
countries.add(i.next());
}
This example will load all countries. Performance
- Loading 239 country object graphs takes an average of 370ms with cacheing off
- Loading 239 country object graphs takes an average of 76ms with cacheing on
- Loading 239 country object graphs takes an average of 281ms with cacheing off and lazy loading of the CreditCard set on.
- Loading 239 country object graphs takes an average of 25ms with cacheing on and lazy loading of the CreditCard set on.
Strategies for Use Session.iterate() should be used in preference to Session.find() to utilise the cache. Additional performance can be achieved by making association collections such as Set load lazily. Enable lazy loading using lazy="true" . An example using hibernate doclet is:
* @hibernate.set role="creditCards" table="COUNTRY_CREDIT_CARD" lazy="true"
Cache Configuration Using cache.ccfJCS is configured in the file cache.ccf. This file must be in the classpath and loadable by this.getClass().getResourceAsStream() by Hibernate. Unfortunately this seems to be problematic for some application servers. In Orion the only thing which seems to work is adding cache.ccf to the jcs.jar and placing jcs.jar in Orion's lib directory. Another Option in Orion is to put the cache.ccf file in =ejb-ap=p directory. A cache.ccf file which works with the above examples is:
# DEFAULT CACHE REGION (memory cache)
jcs.default=DC
jcs.default.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=2000
jcs.default.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.default.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.default.elementattributes.IsEternal=false
jcs.default.elementattributes.MaxLifeSeconds=24000
jcs.default.elementattributes.IdleTime=180000
jcs.default.elementattributes.IsSpool=false
jcs.default.elementattributes.IsRemote=false
jcs.default.elementattributes.IsLateral=false
#Country Cache
jcs.region.com.gregluck.domain.Country=DC
jcs.region.com.gregluck.domain.Country.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.com.gregluck.domain.Country.cacheattributes.MaxObjects=1000
jcs.region.com.gregluck.domain.Country.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.com.gregluck.domain.Country.cacheattributes.UseMemoryShrinker=true
jcs.region.com.gregluck.domain.Country.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.region.com.gregluck.domain.Country.cacheattributes.ShrinkerIntervalSeconds=60
jcs.region.com.gregluck.domain.Country.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.region.com.gregluck.domain.Country.elementattributes.IsEternal=false
jcs.region.com.gregluck.domain.Country.elementattributes.MaxLifeSeconds=240
jcs.region.com.gregluck.domain.Country.elementattributes.IdleTime=180
jcs.region.com.gregluck.domain.Country.elementattributes.IsSpool=false
jcs.region.com.gregluck.domain.Country.elementattributes.IsRemote=false
jcs.region.com.gregluck.domain.Country.elementattributes.IsLateral=false
#Region Cache
jcs.region.com.gregluck.domain.Region=DC
jcs.region.com.gregluck.domain.Region.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.com.gregluck.domain.Region.cacheattributes.MaxObjects=1000
jcs.region.com.gregluck.domain.Region.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.com.gregluck.domain.Region.cacheattributes.UseMemoryShrinker=true
jcs.region.com.gregluck.domain.Region.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.region.com.gregluck.domain.Region.cacheattributes.ShrinkerIntervalSeconds=60
jcs.region.com.gregluck.domain.Region.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.region.com.gregluck.domain.Region.elementattributes.IsEternal=false
jcs.region.com.gregluck.domain.Region.elementattributes.MaxLifeSeconds=240
jcs.region.com.gregluck.domain.Region.elementattributes.IdleTime=180
jcs.region.com.gregluck.domain.Region.elementattributes.IsSpool=false
jcs.region.com.gregluck.domain.Region.elementattributes.IsRemote=false
jcs.region.com.gregluck.domain.Region.elementattributes.IsLateral=false
#CreditCard Cache
jcs.region.com.gregluck.domain.CreditCard=DC
jcs.region.com.gregluck.domain.CreditCard.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.com.gregluck.domain.CreditCard.cacheattributes.MaxObjects=1000
jcs.region.com.gregluck.domain.CreditCard.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.com.gregluck.domain.CreditCard.cacheattributes.UseMemoryShrinker=true
jcs.region.com.gregluck.domain.CreditCard.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.region.com.gregluck.domain.CreditCard.cacheattributes.ShrinkerIntervalSeconds=60
jcs.region.com.gregluck.domain.CreditCard.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.region.com.gregluck.domain.CreditCard.elementattributes.IsEternal=false
jcs.region.com.gregluck.domain.CreditCard.elementattributes.MaxLifeSeconds=240
jcs.region.com.gregluck.domain.CreditCard.elementattributes.IdleTime=180
jcs.region.com.gregluck.domain.CreditCard.elementattributes.IsSpool=false
jcs.region.com.gregluck.domain.CreditCard.elementattributes.IsRemote=false
jcs.region.com.gregluck.domain.CreditCard.elementattributes.IsLateral=false
# System CACHE REGION (unused)
#jcs.system.groupIdCache=DC
#jcs.system.groupIdCache.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
#jcs.system.groupIdCache.cacheattributes.MaxObjects=10000
#jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
#Auxiliary CACHE (disk cache) (unused)
#jcs.auxiliary.DC=org.apache.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
#jcs.auxiliary.DC.attributes=org.apache.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
#jcs.auxiliary.DC.attributes.DiskPath=cache
#Lateral TCP Cache (unused)
#jcs.auxiliary.LTCP=org.apache.jcs.auxiliary.lateral.LateralCacheFactory
#jcs.auxiliary.LTCP.attributes=org.apache.jcs.auxiliary.lateral.LateralCacheAttributes
#jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
#jcs.auxiliary.LTCP.attributes.TcpServers=localhost:1111
#jcs.auxiliary.LTCP.attributes.TcpListenerPort=1110
#jcs.auxiliary.LTCP.attributes.PutOnlyMode=false
In this file separate regions are set up for Country, Region and CreditCard so that different timeouts can be set. Though not shown, regions can also be set up for collections which are member variables e.g. Country/CreditCard.
A warning on IndexedDiskCacheThe default disk cache, invoked with the following line suffers from deadlocks.
jcs.auxiliary.DC=org.apache.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
See http://forum.hibernate.org/viewtopic.php?t=924937 for more information.
Manual Manipulation of JCSThe Hibernate API is just enough for Hibernate to plug JCS into its existing Cache framework. For programmatic manipulation of caches beyond this, you can use the JCS API. See http://jakarta.apache.org/turbine/jcs/apidocs/index.html . With the API you can obtain a CacheManager, then Caches and manipulate those caches.
Obtaining the CompositeCacheManager instanceThe examples shown use the CompositeCacheManager. It is a singleton within a JVM and can be readily obtained from a static getInstance() method.
CompositeCacheManager cacheManager = CompositeCacheManager.getInstance();
Obtaining a CacheYou can list caches with
String[] names = cacheManager.getCacheNames();
for (int i = 0; i < names.length; i++) {
LOG.debug("Cache: " + names[i]);
}
and obtain a cache using:
CompositeCache cache = cacheManager.getCache("com.gregluck.domain.Country");
Emptying a CacheTo empty all objects in a cache you can use:
cache.removeAll();
Warning: This method is known to cause a memory leak. See http://forum.hibernate.org/viewtopic.php?p=2175231#2175231 for more information. Comments: This is an excellent introduction to the uses of JCS in Hibernate. Thank you! -Kevin http://www.kevinelliott.net
|