First-level & second-level cache
Lets start with very basic definition "What is Cache"?
🧠 Think of CACHE as “remembering things”
When your app reads data from DB, it says:
“I don’t want to ask the database again if I already know the answer.”
That memory is called cache.
🔹 First Level Cache (L1 Cache)
🧑💻 Imagine this:
You are working on one task (one request / one transaction).
You ask DB:
“Give me User with id = 1”
DB replies and Hibernate keeps it in its pocket.
Now again you ask:
“Give me User with id = 1”
Hibernate says:
“I already have it in my pocket 😎 No need to ask DB again.”
🔑 Important
In simple words
First Level Cache = Memory for ONE request
💡 Key facts
✅ So,what is it ?
👉 Every time you fetch an entity, Hibernate stores it in the Session cache.
📦 Scope
🔹 Second Level Cache (L2 Cache)
🏢 Imagine this:
Now instead of your pocket, there is a cupboard in office.
Anyone can use it.
1️⃣ First user asks DB:
“Give me User with id = 1”
→ DB is called → Data is saved in cupboard
2️⃣ Second user asks the SAME thing:
“Give me User with id = 1”
Hibernate checks cupboard:
“Oh, already there 👍 No DB call”
🔑 Important
In simple words
Second Level Cache = Memory for ENTIRE application
✅ So,what is it ?
📦 Scope
🔧 Requires configuration
Hibernate itself doesn’t store data — it uses providers like:
🤏 One-line definition (remember this)
🟢 First Level Cache → remembers data inside one request 🔵 Second Level Cache → remembers data for all requests
Let me give you a VERY REAL, VERY COMMON example
🎯 Scenario
API: Update user email Requirement:
👉 All this happens in ONE API call.
🧑💻 Controller (API hit ONCE)
@PutMapping("/users/{id}/email")
public void updateEmail(@PathVariable Long id,
@RequestParam String email) {
userService.updateEmail(id, email);
}
✔ API is called once
🧑💻 Service (DB hit multiple times)
@Transactional
public void updateEmail(Long id, String email) {
// 1️⃣ Check if user exists
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
// 2️⃣ Validation step (accidentally hits DB again)
if (!userRepository.findById(id).get().isActive()) {
throw new RuntimeException("Inactive user");
}
// 3️⃣ Update
user.setEmail(email);
userRepository.save(user);
}
🔍 What DB calls look like
❌ Without First Level Cache (theoretical)
SELECT * FROM user WHERE id = ?
SELECT * FROM user WHERE id = ?
UPDATE user SET email = ?
✅ With First Level Cache (real Hibernate behavior)
SELECT * FROM user WHERE id = ?
UPDATE user SET email = ?
👉 Second findById(id) ✔ No DB hit ✔ Returned from First Level Cache
🧠 WHY this happens (important)
Hibernate says:
“I already loaded User(id) in THIS transaction. I’ll reuse it.”
Now we’ll do the SAME style example, but this time for Second Level Cache (L2 cache) — no new concepts, just multiple requests instead of one.
🎯 Scenario (VERY common in real apps)
API: Get user profile Data: User details (rarely change)
🧑💻 Controller
(API is hit MANY TIMES by different users)
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
🧑💻 Service
@Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
🧪 Requests timeline (THIS is THE KEY)
🔹 Request 1
User A → GET /users/1
What happens:
L1 cache → empty
L2 cache → empty
DB → HIT ✅
Data saved in L1 + L2
🔹 Request 2
User B → GET /users/1
What happens:
L1 cache → empty (new transaction)
L2 cache → HIT ✅
DB → NOT called ❌
🔹 Request 3
User C → GET /users/1
What happens:
L1 → empty
L2 → HIT
DB → NOT called
🧠 DB query comparison
❌ Without L2 cache
Request 1 → DB hit
Request 2 → DB hit
Request 3 → DB hit
✅ With L2 cache
Request 1 → DB hit
Request 2 → NO DB hit
Request 3 → NO DB hit
🧠 Why this CANNOT be done by L1 cache
Because:
👉 Only L2 cache survives.
🧩 One-line definition (lock this in 🔒)
Second Level Cache avoids repeated database queries for the same data across multiple requests and transactions.
🎤 Interview-ready answer
Second level cache works at the SessionFactory level and stores entities across multiple transactions. It is useful for read-heavy and rarely changing data, such as master tables.
Hope you liked this article and found it helpful.