Friday, February 10, 2012

JPA Caching in EclipseLink 2.3.2

Best Practice in JPA series:
Part 1 – JPA Caching in EclipseLink
Part 2 – One-to-one relationship using EclipseLink

Java Persistence API 2.0 defines Level 1 (L1) Cache (Entity Cache), Level 2 (L2) Cache (Shared Entity Cache) and Query (Result) Cache. Now I can take full advantage of JPA cache, just like what I did 5, 6 years ago using Hibernate cache. Although Hibernate also has its JPA implementation, I found EclipseLink has better default settings and also easier to enable advanced features. Here are some random tips.

The shared attribute of @Cache annotation has deprecated and is replaced by isolation=CacheIsolationType.SHARED, which means sharing entity cache between EntityManager objects and allowing query cache (if enabled) to use entity cache. Even you don't set @Cache annotation on a domain object, it's enabled by default.

CacheIsolationType.PROTECTED means sharing entity cache between EntityManager objects but disallowing query cache to use entity cache, even when query cache is enabled.

CacheIsolationType.ISOLATED means not sharing entity cache between EntityManager objects and disallow query cache to use entity cache, even when query cache is enabled.

The default coordinationType=CacheCoordinationType.SEND_OBJECT_CHANGES in @Cache means any entity update to database also updates entity in cache, which is great in performance.

Set hints = { @QueryHint(name = QueryHints.QUERY_RESULTS_CACHE, value = HintValues.TRUE) } in @NamedQuery if you want to enable query cache. But unless you have a fixed domain objects, don't enable its query cache. Note that "eclipselink.query-results-cache" is not a standard JPA hint, you cannot set it to "True" for a javax.persistence.Query object.

Unless you have every domain object in entity cache, don't use hints = { @QueryHint(name = QueryHints.CACHE_USAGE, value = CacheUsage.CheckCacheOnly) } in @NamedQuery.

Set <property name="eclipselink.logging.level" value="FINE" /> in META-INFO/persistence.xml to show SQL statements.

Unless you want to change the default cache retrieve / store mode, don't need to set following properties to EntityManager object.
em.setProperty(QueryHints.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
em.setProperty(QueryHints.CACHE_STORE_MODE, CacheStoreMode.USE);

Even you don't set any hint in @NamedQuery, query result will be used to populate entity cache.

Only entityManager.find(entityClass, id) can use entity cache. Get entity, or get entities, by any field(s) other than id don't use entity cache.

The default sizes of entity cache and query cache are 100 each.

If you get
Exception in thread "main" java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Syntax error parsing the query [from Entity], line 1, column 0: unexpected token [from].
Internal Exception: NoViableAltException(33@[])
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(
change your JPA query to entityManager.createQuery("select entity from Entity entity").getResultList();

Please feel free to let me know if you have any questions regarding EclipseLink cache.

Tuesday, February 07, 2012

Dependency Lock-in

Update 16/2/2012: I read Apache Camel 2.9 - Reduced dependency on Spring JARs after I wrote following post and wish you to have a read at it as well.

Like version lock-in, dependency lock-in is another thing developers should pay attention to. It happens both when you are developing your own product, and when you are using 3rd party products.

When you're planning your product, it's better not to make any assumption of how it'll be used. Even it IS a web application, you will focus on the core functions first, and later make these core functions accessible via a browser. By thinking and doing this way, you will never make one of your function depending on an HttpServletRequest. The request from web is just one of the many ways to your product.

On the other hand, things become a bit tricky when you include a 3rd party component, especially when including a new dependency in Maven. What you can control is groupId, artifactId and latest version. What does that component depend on is usually out of your control. However it's your responsibility to make sure all the dependencies (and their recursive dependencies) work together happily.

In one of my hobby projects, I want to use Apache CXF to expose core functions to RESTful web services and JSON format, so I include the latest version of Apache CXF Runtime JAX-RS Frontend, org.apache.cxf:cxf-rt-frontend-jaxrs:jar:2.5.2. After running mvn eclipse:eclipse, I found my project is locked in to Spring Framework 3.0.6. org.springframework:spring-core:jar:3.0.6.RELEASE is directly included, and org.springframework:spring-web:jar:3.0.6.RELEASE is indirectly included by org.apache.cxf:cxf-rt-frontend-jaxrs:jar:2.5.2. Please look at the follow dependency tree.

+- org.apache.cxf:cxf-rt-frontend-jaxrs:jar:2.5.2:compile
|  +- org.springframework:spring-core:jar:3.0.6.RELEASE:compile
|  |  +- org.springframework:spring-asm:jar:3.0.6.RELEASE:compile

|  +- org.apache.cxf:cxf-rt-transports-http:jar:2.5.2:compile
|  |  +- org.apache.cxf:cxf-rt-transports-common:jar:2.5.2:compile
|  |  \- org.springframework:spring-web:jar:3.0.6.RELEASE:compile
|  |     +- org.springframework:spring-beans:jar:3.0.6.RELEASE:compile
|  |     \- org.springframework:spring-context:jar:3.0.6.RELEASE:compile
|  |        +- org.springframework:spring-aop:jar:3.0.6.RELEASE:compile
|  |        \- org.springframework:spring-expression:jar:3.0.6.RELEASE:compile

I do decide to use Spring, but don't want to be decided by Apache CXF. Moreover, I'd like to use Spring 3.1.0. It's not hard to solve this problem by modifying pom.xml.



Don't dependency lock-in yourself, and don't dependency lock-in the users of your products.

Happy coding!