First Level Cache in Hibernate
Complete guide to Hibernate First Level Cache with architecture, internal workflow, examples, performance benefits, persistence context behavior, and interview questions.
What is First Level Cache?
First Level Cache is Hibernate's built-in cache mechanism.
Every Hibernate Session (or JPA Persistence Context) maintains its own cache.
Whenever an entity is loaded from the database, Hibernate stores it in memory.
Subsequent requests for the same entity are served directly from memory without hitting the database again.
Database Query Once
↓
Store in Session Cache
↓
Reuse Multiple Times
Why First Level Cache Exists
Imagine a service method:
@Transactional
public void processEmployee() {
Employee employee1 =
repository.findById(1L).get();
Employee employee2 =
repository.findById(1L).get();
Employee employee3 =
repository.findById(1L).get();
}
Without cache:
SELECT ...
SELECT ...
SELECT ...
3 Database Calls
With First Level Cache:
SELECT ...
Cache Hit
Cache Hit
Only 1 Database Call
Real Life Example
Think about a teacher taking attendance.
First time:
Teacher checks school records.
Second time:
Teacher remembers student details.
No need to check records again.
Hibernate behaves similarly.
Database = School Records
Session Cache = Teacher Memory
Where Does First Level Cache Live?
Hibernate Session
OR
Persistence Context
Every Session has its own cache.
Architecture Diagram
flowchart TD
A[Application]
B[Hibernate Session]
C[First Level Cache]
D[(Database)]
A --> B
B --> C
C -->|Cache Miss| D
D --> C
C --> A
Relationship with Persistence Context
Many developers think:
Persistence Context
and
First Level Cache
are different.
Actually:
Persistence Context
=
First Level Cache
Hibernate stores managed entities here.
Sample Entity
@Entity
@Table(name = "employees")
public class Employee {
@Id
private Long id;
private String name;
private Double salary;
}
Example 1 - Cache Hit
@Transactional
public void demo() {
Employee e1 =
entityManager.find(Employee.class, 1L);
Employee e2 =
entityManager.find(Employee.class, 1L);
}
Generated SQL:
select * from employees where id=1;
Only one query.
Second call comes from cache.
Internal Flow
flowchart LR
A[find Employee]
B{In Cache?}
C[Return Cached Entity]
D[Execute SQL]
E[Store In Cache]
A --> B
B -->|Yes| C
B -->|No| D
D --> E
E --> C
Detailed Execution
First Call
entityManager.find(Employee.class, 1L);
Hibernate:
Cache Lookup
↓
Not Found
↓
SQL Executed
↓
Store Entity in Cache
SQL:
SELECT * FROM employees WHERE id=1;
Second Call
entityManager.find(Employee.class, 1L);
Hibernate:
Cache Lookup
↓
Found
↓
Return Cached Object
No SQL.
Object Reference Proof
Employee e1 =
entityManager.find(Employee.class, 1L);
Employee e2 =
entityManager.find(Employee.class, 1L);
System.out.println(e1 == e2);
Output:
true
Same Java Object.
Visualization
Session Cache
+--------------------+
| Employee ID = 1 |
| Name = Venu |
| Salary = 100000 |
+--------------------+
e1 ----------+
|
+------> Same Object
|
e2 ----------+
First Level Cache and Dirty Checking
These two concepts work together.
Employee employee =
entityManager.find(Employee.class, 1L);
employee.setSalary(200000);
Entity already exists in cache.
Hibernate detects changes during commit.
UPDATE employees
SET salary=200000
WHERE id=1;
Cache + Dirty Checking Flow
flowchart TD
A[Load Entity]
B[Store In Cache]
C[Modify Entity]
D[Dirty Check]
E[Generate Update]
A --> B
B --> C
C --> D
D --> E
Example with Spring Data JPA
@Service
@Transactional
public class EmployeeService {
@Autowired
private EmployeeRepository repository;
public void process() {
Employee e1 =
repository.findById(1L).get();
Employee e2 =
repository.findById(1L).get();
System.out.println(e1 == e2);
}
}
Output:
true
One SQL only.
What Happens Across Transactions?
Transaction 1
Employee e1 =
repository.findById(1L).get();
SQL Executes.
Transaction 2
Employee e2 =
repository.findById(1L).get();
SQL Executes Again.
Why?
Because cache is Session scoped.
Session Scope Diagram
flowchart LR
A[Session 1]
B[First Level Cache]
C[(DB)]
D[Session 2]
E[New First Level Cache]
A --> B
B --> C
D --> E
E --> C
Caches are not shared.
Cache Eviction Using detach()
Employee employee =
entityManager.find(Employee.class, 1L);
entityManager.detach(employee);
Removed from cache.
Diagram
Before detach
Cache
└── Employee(1)
After detach
Cache
└── Empty
Example
Employee e1 =
entityManager.find(Employee.class, 1L);
entityManager.detach(e1);
Employee e2 =
entityManager.find(Employee.class, 1L);
SQL Executed Twice.
Clear Entire Cache
entityManager.clear();
Removes all managed entities.
Example
Employee e1 =
entityManager.find(Employee.class, 1L);
entityManager.clear();
Employee e2 =
entityManager.find(Employee.class, 1L);
SQL:
SELECT * FROM employees WHERE id=1;
SELECT * FROM employees WHERE id=1;
Refresh Entity
Suppose another application updates DB.
Current cache contains old value.
entityManager.refresh(employee);
Hibernate reloads data.
SQL:
SELECT * FROM employees WHERE id=1;
Refresh Diagram
flowchart LR
A["Cache Entity"]
B["Database Changed"]
C["EntityManager Refresh"]
D["Reload From Database"]
A --> C
B --> C
C --> D
Batch Processing Issue
Large batches can fill cache.
Example:
for(Employee e : employees){
e.setSalary(1000);
}
Suppose:
100,000 Employees
All remain in memory.
Potential:
OutOfMemoryError
Best Practice
for(Employee e : employees){
e.setSalary(1000);
if(count % 50 == 0){
entityManager.flush();
entityManager.clear();
}
}
Memory Visualization
Bad:
Cache
Employee1
Employee2
Employee3
...
Employee100000
Good:
Process 50
Flush
Clear
Process Next 50
Flush
Clear
First Level Cache vs Second Level Cache
| Feature | First Level Cache | Second Level Cache |
|---|---|---|
| Default | Yes | No |
| Scope | Session | Application |
| Shared | No | Yes |
| Enabled | Automatic | Configuration Required |
| Storage | Memory | Memory/Redis/Ehcache |
| Lifetime | Session | Across Sessions |
Complete Lifecycle Diagram
flowchart TD
A[Find Entity]
B{Exists In Cache?}
C[Return Entity]
D[SQL Query]
E[Store In Cache]
F[Modify Entity]
G[Dirty Checking]
H[Update SQL]
A --> B
B -->|Yes| C
B -->|No| D
D --> E
E --> C
C --> F
F --> G
G --> H
Performance Benefits
Without Cache:
100 Entity Reads
=
100 Database Calls
With Cache:
100 Entity Reads
=
1 Database Call
+
99 Cache Hits
Benefits:
✅ Reduced Database Load
✅ Faster Response Time
✅ Fewer Network Calls
✅ Better Throughput
Common Interview Questions
Q1. What is First Level Cache?
Session-scoped cache maintained by Hibernate.
Q2. Is First Level Cache enabled by default?
Yes.
Always enabled.
Q3. Can we disable First Level Cache?
Not completely.
It is part of Session/Persistence Context.
Q4. What is stored inside First Level Cache?
Managed Entities
Snapshots
Proxy Objects
Q5. Is First Level Cache shared?
No.
Each Session has its own cache.
Q6. Difference between clear() and detach()?
detach(entity);
Removes one entity.
clear();
Removes all entities.
Q7. Why does Hibernate return same object reference?
Because cached entity instance is reused.
e1 == e2
returns:
true
Best Practices
✅ Keep transactions short
✅ Use clear() in batch jobs
✅ Understand Session boundaries
✅ Use refresh() when data changes externally
✅ Leverage cache to reduce database hits
❌ Don't keep huge persistence contexts
❌ Don't assume cache works across transactions
❌ Don't confuse First Level Cache with Redis/Ehcache
Summary
First Level Cache is Hibernate's Session-level cache.
Workflow:
Entity Request
↓
Check Cache
↓
Cache Miss → Database
↓
Store In Cache
↓
Future Requests
↓
Cache Hit
Remember:
First Level Cache
=
Persistence Context
=
Managed Entity Store
And it is the foundation for:
✔ Dirty Checking
✔ Entity Lifecycle Management
✔ Performance Optimization
✔ Hibernate's ORM Magic