Hibernate Second-Level Cache
So Before we start Second-Level Cache or L2 Cache let’s first recall First-Level Cache provided by Hibernate, This is the Very High Level…
Hibernate Second-Level Cache
So Before we start Second-Level Cache or L2 Cache let’s first recall First-Level Cache provided by Hibernate, This is the Very High Level Explanation of it, But If you want just tell me Will add it soon.
First-Level Cache (Session Cache)
The First-Level Cache is Hibernate’s built-in cache associated with a single **Session (or `EntityManager` in JPA). It is enabled by default and cannot be disabled**.
Whenever an entity is requested, Hibernate first checks the session cache. If the entity is already present, it returns the cached instance instead of querying the database.
Characteristics
- Built into Hibernate.
- Scope: One
Session. - Not shared across sessions.
- Stores managed entities.
- Cleared when the session is closed or explicitly cleared (
clear(),evict()).

So in this JPA architechture The Persistence Context is the main Componet of First-Level Chaching.

So this is the Entity Lifecycle In Persistence Context, Just let me know If you don’t understand it, i will explain it in depth in another article.
Example
Session session = sessionFactory.openSession();
User user1 = session.find(User.class, 1L); // Database hit
User user2 = session.find(User.class, 1L); // Returned from First-Level Cache
System.out.println(user1 == user2); // true
Key Takeaway:
One Session = One First-Level Cache. Repeated access to the same entity within a session avoids unnecessary database queries
Now let’s Move Forward to the Todays main Agenda L2 Cache.
so in Second level caching we are going to achieve this:

So we maintain one Second level cache for each get call or hibernate session a Common cache unlike First level cache which is not shared among all the sessions/calls so yeah this is the basic idea.
And to enable 2nd level cache we have to do these steps
1st add this dependencies inside the POM.xml
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>6.5.2.Final</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
why this three dependencies ?:

<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
This provides the core implementation of Second Level caching we can choose another implementations like caffeine or hazelcast
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>6.5.2.Final</version>
</dependency>
Hibernate specific Caching logic comes with this.Like we use Annotations over entity @Cache, we used CacheConcurrencyStrategy, so specific logic need to be executed, and this library help us with that.
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
Provides the interface for Jcache, hibernate interact with theseAPIs.Helps to achieve Loose coupling. We can change from Ehcache to some other Jcache compliant caching provider without changing code.
2nd step is to Add applications.properties
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
// enables second level caching
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
spring.jpa.properties.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider
// cache provider like EhCache,Caffeine or hazelcast
logging.level.org.hibernate.cache.spi=DEBUG
//enables DEBUG logging for Hibernate's cache subsystem
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
let’s talk about this Seperatly : what the use of it what region is ?
Region: Helps in logical grouping of cached data.
For each Region (or say group), we can apply different caching strategies like:
- Eviction policy
- TTL
- Cache size
- Concurrency strategy, etc.
Which helps in achieving granular-level management of cached data (either Entity, Collection, or Query results).
@Entity
@Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "userDetailsCache"
)
public class UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Constructors
public UserDetails() {
}
public UserDetails(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and setters
}
@Entity
@Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "orderDetailsCache"
)
public class OrderDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private int quantity;
private double price;
// Getters and Setters
}
ehcache.xml (inside src/main/resources)
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd">
<cache alias="userDetailsCache"
maxElementsInMemory="100"
timeToLiveSeconds="60"
evictionStrategy="LIFO" />
<cache alias="orderDetailsCache"
maxElementsInMemory="1000"
timeToLiveSeconds="200"
evictionStrategy="FIFO" />
</ehcache>
3rd step we have already discussed
@Entity
@Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "userDetailsCache"
)
public class UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Constructors
public UserDetails() {
}
public UserDetails(String name, String email) {
this.name = name;
this.email = email;
}
// Getters and setters
}
adding
@Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "userDetailsCache"
)
on Entity.
so u might be Wondering what is this:- usage = CacheConcurrencyStrategy ?
If multiple transactions are accessing the same cached entity simultaneously, what strategy should Hibernate use to keep the cache consistent?
there are mainly 4 type Strategy…
- READ_ONLY
- READ_WRITE
- NONSTRICT_READ_WRITE
- TRANSACTIONAL
let go through them one by one:-
1.READ_ONLY
Read-only access to the shared second-level cache.
Indicates that the cached object is immutable, and is never updated. If an entity with this cache concurrency is updated, an exception is thrown. This is the simplest, safest, and best-performing cache concurrency strategy. It’s particularly suitable for so-called “reference” data.
2.READ_WRITE
Read/write access to the shared second-level cache using soft locks.
Indicates a non-vanishing likelihood that two concurrent transactions attempt to update the same item of data simultaneously. This strategy uses “soft” locks to prevent concurrent transactions from retrieving or storing a stale item from or in the cache during the transaction completion process. A soft lock is simply a marker entry placed in the cache while the updating transaction completes.
- A second transaction may not read the item from the cache while the soft lock is present, and instead simply proceeds to read the item directly from the database, exactly as if a regular cache miss had occurred.
- Similarly, the soft lock also prevents this second transaction from storing a stale item to the cache when it returns from its round trip to the database with something that might not quite be the latest version.
- for reading Multiple transactions can read the same cache entry simultaneously.

This concurrency strategy is not compatible with serialisable transaction isolation.
3.NONSTRICT_READ_WRITE
Read/write access to the shared second-level cache with no locking.
Indicates that the cached object is sometimes updated, but that it is extremely unlikely that two transactions will attempt to update the same item of data at the same time.
This strategy does not use locks. When an item is updated, the cache is invalidated both before and after completion of the updating transaction.
But without locking, it’s impossible to completely rule out the possibility of a second transaction storing or retrieving stale data in or from the cache during the completion process of the first transaction.
Step Reason Before update Remove the old cached value so new reads are less likely to get stale data from the cache. After commit Remove any stale value that may have been repopulated while the transaction was in progress.
like READ_WRITE This concurrency strategy is not compatible with serialisable transaction isolation.
Normal Read :

Update :

4.TRANSACTIONAL
TRANSACTIONAL is a second-level cache strategy where the cache and the database participate in the same distributed transaction (using JTA/XA). Instead of Hibernate maintaining consistency with soft locks, a transactional cache provider and transaction manager ensure that both the cache and the database either commit together or roll back together.
It uses something called JTA/XA.. which i guess not needed for this context..will cover it in HLD.
메타데이터
- post_id
- fbce52e412cb
- slug
- hibernate-second-level-cache-fbce52e412cb
- url
- https://medium.com/@darshanpathak8899/hibernate-second-level-cache-fbce52e412cb
- canonical_url
- https://medium.com/@darshanpathak8899/hibernate-second-level-cache-fbce52e412cb
- author_url
- https://medium.com/@darshanpathak8899
- status
- ok
- fetched_at
- 2026-07-09 15:12:33