First-level & second-level cache

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

  • This pocket exists ONLY while you are working
  • Once work is finished → pocket is emptied

In simple words

First Level Cache = Memory for ONE request

💡 Key facts

  • Always ON
  • You cannot turn it off
  • Exists per request / transaction

✅ So,what is it ?

  • Default cache
  • Enabled automatically
  • Associated with a single Hibernate Session / JPA EntityManager

👉 Every time you fetch an entity, Hibernate stores it in the Session cache.

📦 Scope

  • Session-level
  • Destroyed when session is closed


🔹 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

  • Shared by all users
  • Survives multiple requests
  • Must be explicitly enabled

In simple words

Second Level Cache = Memory for ENTIRE application

✅ So,what is it ?

  • Optional cache
  • Shared across multiple sessions
  • Works at SessionFactory level

📦 Scope

  • Application-level
  • Data survives session close

🔧 Requires configuration

Hibernate itself doesn’t store data — it uses providers like:

  • Ehcache
  • Redis
  • Hazelcast
  • Infinispan

Article content

🤏 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:

  1. Check if user exists
  2. Validate user status
  3. Update email

👉 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:

  • Each request = new transaction
  • Each transaction = new Hibernate Session
  • L1 cache dies after request

👉 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.

To view or add a comment, sign in

More articles by Saumya Garg

Explore content categories