What is the difference between first-level cache and second-level cache in JPA?
In JPA (Java Persistence API), caching plays a crucial role in improving application performance by reducing the number of database access operations. JPA defines two primary levels of caching: the first-level cache and the second-level cache, each with distinct scopes and purposes.
First-Level Cache (Persistence Context Cache)
The first-level cache, also known as the Persistence Context cache or transactional cache, is an essential part of every JPA application. It is a mandatory cache and is always active. Each EntityManager instance has its own first-level cache, meaning it's tied to a specific persistence context and is not shared across different EntityManager instances.
- Scope: Transactional and session-specific. It lives as long as the
EntityManageror the transaction associated with it. - Sharing: Not shared. Each
EntityManagerhas its own isolated cache. - Mechanism: When an entity is loaded or persisted, it is placed in this cache. Subsequent requests for the same entity within the same persistence context will return the cached instance, avoiding a database round trip.
- Automatic: It's always enabled and managed by JPA. Developers don't explicitly enable or disable it.
- Purpose: Ensures identity equality (only one instance of a persistent entity exists within a persistence context) and improves performance within a single transaction.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// First read: entity is loaded from DB and placed in first-level cache
Product product1 = em.find(Product.class, 1L);
System.out.println("Product 1: " + product1.getName());
// Second read: entity is retrieved from first-level cache, no DB access
Product product2 = em.find(Product.class, 1L);
System.out.println("Product 2: " + product2.getName());
// product1 == product2 will be true
System.out.println("Are product1 and product2 the same instance? " + (product1 == product2));
em.getTransaction().commit();
em.close();
Second-Level Cache (Shared Cache)
The second-level cache, also known as the shared cache or application-level cache, is an optional cache that can be configured by the developer. Unlike the first-level cache, it is shared across all EntityManager instances within the same EntityManagerFactory and even across different applications in a clustered environment (depending on implementation). It stores entity data (not entity objects themselves) after an entity manager is closed, making it available for subsequent EntityManager instances.
- Scope: Application-level and shared. It lives as long as the
EntityManagerFactoryor until explicitly cleared. - Sharing: Shared across all
EntityManagerinstances created by the sameEntityManagerFactory. - Mechanism: Stores entity data (e.g., field values) in a non-transactional region. When an entity is requested, JPA first checks the first-level cache. If not found, it checks the second-level cache before querying the database.
- Optional: Must be explicitly enabled and configured, typically via
persistence.xmlor annotations. - Purpose: Reduces database load and improves performance significantly across multiple transactions and users.
- Concurrency: Requires careful handling of concurrency strategies (e.g., READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) to ensure data consistency.
<persistence-unit name="myPU">
<properties>
<!-- Enable second-level cache -->
<property name="jakarta.persistence.sharedCache.mode" value="ENABLE_SELECTIVE"/>
<!-- Hibernate-specific configuration for second-level cache provider -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
</properties>
</persistence-unit>
<!-- Example for an entity to be cached -->
@Entity
@Cacheable
public class Product {
@Id
private Long id;
private String name;
// ...
}
Key Differences Summarized
| Feature | First-Level Cache | Second-Level Cache |
|---|---|---|
| Scope | Persistence Context (Transactional) | EntityManagerFactory (Application-wide) |
| Sharing | Not shared (per EntityManager) | Shared (across all EntityManager instances) |
| Mandatory/Optional | Mandatory and always active | Optional and configurable |
| Lifetime | As long as EntityManager/transaction | As long as EntityManagerFactory/application |
| Content | Actual entity objects | Entity data (field values) |
| Purpose | Identity equality, transactional performance | Reduce DB load, application-wide performance |
When to Use Each
The first-level cache is always in use and requires no configuration. The second-level cache is highly beneficial for frequently accessed, relatively static data that is read more often than it is updated. It significantly reduces database load and network traffic for read-heavy applications, but it introduces complexity regarding data consistency, especially in distributed environments. Careful consideration of cache concurrency strategies is crucial when enabling the second-level cache.