Here are some notes on L2 (level 2/shared) cache in JPA
Quick facts
- JPA L2 cache was standardized in JPA 2.0 (Java EE 6)
- Based on EntityManagerFactory i.e. its a global cache common across all Persistence Units
- A layer which site between the L1 cache (EM) and the database i.e. the database is queried only if the entity is not in the L1 cache and L2 cache
- This feature is optional (but most of the well known providers support it)
Initial setup
@Cacheable: this annotation is used to declaratively configure L2 (shared) caching for a JPA entity
Cache Modes
Specified in persistence.xml or during Entity Manager Factory creation (programmatic)
- NOT_SPECIFIED – default mode. Provider specific behavior (e.g. Eclipselink uses L2 cache by default vs Hibernate which needs explicit configuration for the same)
- ENABLE_SELECTIVE – only entity classes with @Cacheable(true) are cached
- DISABLE_SELECTIVE – entity classes with @Cacheable(false) are not cached
- ALL – all the JPA entities are cached
- NONE – none of the entities are cached
A JPA persistence.xml demonstrating usage of L2 caching config parameter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> | |
<persistence-unit name="jap-punitxml-testPU" transaction-type="JTA"> | |
<exclude-unlisted-classes>false</exclude-unlisted-classes> | |
…………… | |
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> | |
<properties> | |
<property name="javax.persistence.query.timeout" value="3000"/> | |
<property name="javax.persistence.lock.timeout" value="2000"/> | |
</properties> | |
…………… | |
</persistence-unit> | |
</persistence> |
Here is a simple JPA entity with explicit L2 caching metadata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
@Table("EMPLOYEES") | |
@Cacheable(true) | |
public class Employee { | |
//rest of the code… | |
} |
Override mode
In addition to the basic configuration options specified above, JPA allows you to dynamically specify how you want to interact with the L2 cache. These options are available for the entities which have been explicitly configured to participate in L2 caching
They can be enforced using the following methods
- Entity Manager property
- find method of Entity Manager
- hint during query execution
Read options
These options come into play during reading/fetching data from the cache and can be specified using the javax.persistence.cache.retrieveMode property
- CacheRetrieveMode.USE: read from L2 cache
- CacheRetrieveMode.BYPASS: skip the cache (refer the database directly)
Indicate L2 cache usage via EM property
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Stateless | |
public class EmployeeManager{ | |
@persistenceContext("emp_PU") | |
EntityManager em; | |
public Employee getByID(String id){ | |
em.setProperty("javax.persistence.cache.retrieveMode", CacheRetrieveMode.USE); //look for the employee in the L2 cache first | |
return em.find(Employee.class, id); | |
} | |
} |
Write options
These options are used to configure operations related to putting data into the cache and can be specified using the javax.persistence.cache.storeMode property
- CacheStoreMode.USE: populate the cache in response to the executed operation
- CacheStoreMode.BYPASS: do not modify the cache contents post operation
- CacheStoreMode.RERESH: to be used when your database is affected by external sources other than your application itself. Using this option forces the JPA provider to skip the cache (during fetch), go to the database and populate the result into the cache (effectively, a refresh operation)
Note: CacheRetrieveMode and CacheStoreMode are enums
Explicit L2 cache overrides using EM and query hints
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Stateless | |
public class YetAnotherEmployeeManager { | |
@persistenceContext("emp_PU") | |
EntityManager em; | |
public Employee getByPrimaryKey(String employeeID){ | |
Map<String,Object> searchProp = new HashMap<>(); | |
searchProp.put("javax.persistence.cache.storeMode", CacheStoreMode.BYPASS); //don't put the employee into the L2 cache | |
return em.find(Employee.class, employeeID); | |
} | |
public Employee getByOrganization(String orgID){ | |
TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employees e WHERE e.orgID = :orgId", Employee.class); | |
query.setHint("javax.persistence.cache.storeMode", CacheRetrieveMode.REFRESH); | |
query.setParameter("orgId", orgId); | |
return query.getSingleResult(); | |
} | |
} |
Other stuff . . .
Other L2 cache related methods exposed by the javax.persistence.Cache interface (these are self explanatory)
- contains: is this entity in the L2 cache ?
- evict: remove this entity from the cache
- evictAll: clear the shared cache
Interrogating the JPA L2 cache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Stateless | |
public class JPACacheOps{ | |
@persistenceContext("emp_PU") | |
EntityManager em; | |
public void testL2Cache(String id){ | |
String id = "007"; | |
String anotherID = "42"; | |
em.find(Employee.class, id); | |
em.find(Employee.class, id); | |
//both '007' and '42' are in L2 cache | |
String orgID = "org123"; | |
em.find(Org.class, orgID); //'org123' is now in L2 cache | |
Cache sharedL2Cache = em.getEntityManagerFactory().getCache(); | |
sharedL2Cache.contains(Employee.class , id); //returns true | |
sharedL2Cache.evict(Employee.class, id); //'007' removed from shared cache | |
sharedL2Cache.contains(Employee.class, id); //returns false | |
sharedL2Cache.evict(Employee.class); //ALL 'Employee' entities removed | |
sharedL2Cache.contains(Employee.class, anotherID); //returns false | |
sharedL2Cache.contains(Org.class , orgID); //returns true | |
sharedL2Cache.evictAll(); //ALL entities removed | |
sharedL2Cache.contains(Ord.class, orgID); //returns false | |
} | |
} |
Cheers!
Pingback: Java Weekly 22/16: JDBC insert, L2 caching, eager optimization
I had problems when using JPA+cache on glassfish. A simplest case of using JPA, one might say:
– load an entity
– modify it (using its setters)
– abort transaction (throw an exception)
The problem was that I was modifying a shared object and other transactions could see the change although it was not committed.
LikeLike
Hi Rycho
More details would help. Things like JPA persistence context type etc. This is strange – a tx scoped EM should never behave in this manner
LikeLike
Thanks !!! Definitely Helpful
LikeLike
Glad you liked found it useful Arun. Thanks for the feedback
LikeLike
Very informative. Do you have any example of a cache’d Entity containing an collection?
LikeLike