Tuesday, February 03, 2009

JPA / EJB3 Persistence and EhCache

We had an issue with a particular web page that took too long to load. We could have done a whole lot of code optimization, but we thought we'd give a second level cache a try, since it's something we needed to do anyway. The bottom line is that it gave us a significant boost in performance, for very little additional work.

We are using Spring, JPA/EJB3, and Wicket. After some investigation, Ehcache seemed like the best choice because it's popular, it's free and it's very well supported.

We only had time for the absolutely minimal implementation. Fortunately, all we needed to do was add an annotation to the entities we wanted cached, edit persistence.xml, and add ehcache.xml. We didn't even have to add ehcache as a Maven dependency because Hibernate's EJB3 implementation (hibernate-entitymanager) was already depending on it.

src/main/java/com/my/Foo.java


...
@Entity
@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Foo implements Serializable {
...


We are using NONSTRICT_READ_WRITE as our strategy but to be frank, we copied this from an example and it seems to work fine.

src/main/resources/META-INF/persistence.xml


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

<persistence-unit name="default" transaction-type="RESOURCE_LOCAL">

<properties>
...

<!-- 2nd level cache -->
<property name="hibernate.cache.provider_class"
value="net.sf.ehcache.hibernate.SingletonEhCacheProvider" />
<property name="net.sf.ehcache.configurationResourceName"
value="/ehcache.xml" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.use_second_level_cache"
value="true" />
<property name="hibernate.generate_statistics" value="true" />

</properties>
</persistence-unit>
</persistence>


The key piece of information here is that we are using SingletonEhCacheProvider. If you don't do this, you'll get new instances of your entities when cached versions are what you really want.

src/main/resources/ehcache.xml


<ehcache>

<diskStore path="java.io.tmpdir"/>

<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>

<cache name="com.my.Foo"
maxElementsInMemory="1000"/>

</ehcache>


We used the defaults for all the settings. You have to enumerate all the entities you want cached in ehcache.xml. And that's it!

7 comments:

Murat said...

Thanks very quick and helpfull.

Fer nando Franzini said...

Hi

Dont I need to set Query.setHint(”org.hibernate.cacheable”, true); ? to cache work ?

Julian Sinai said...

Fernando, that is for query caching, not entity caching.

rumana said...

Is it not necessary to use @cacheable to mark an entity as cacheable before the @cache annotation....

@Entity
@Cacheable
@Cache( usage=CacheConcurrencyStrategy.READ_WRITE)

anurag.bhatia99 said...

Is there anyway I can see the cache hits?

Balakrishnan B said...

Short and helpful note. Do you know if this config is sufficient to make it work even in a distributed app server setup?

Bala

Hammad Dar said...

CacheConcurrencyStrategy.NONSTRICT_READ_WRITE is hibernate reference and not JPA reference.