Hibernate Entity Dirty Check Example
Hibernate is a popular ORM (Object Relational Mapping) tool in Java that automatically maps Java objects to database tables. One of its powerful features is the dirty checking mechanism. It helps in automatically detecting changes in an object and updating the database accordingly without needing explicit SQL queries. Let us delve into understanding how Java Hibernate performs a dirty check on an entity during persistence operations.
1. What is Dirty Checking in Hibernate?
Dirty checking is the process by which Hibernate automatically tracks changes made to persistent objects in a session. If any object is modified, Hibernate marks it as dirty and generates the necessary UPDATE
statements when the transaction is committed.
1.1 How Does Dirty Checking Work?
When an entity is loaded into a Hibernate session, Hibernate keeps a snapshot of its original state. At flush or commit time, Hibernate compares the current state of the object with its snapshot. If it detects any changes, it issues an UPDATE
statement.
- Tracking happens at the property level.
- Only modified fields are updated in the database.
2. Code Example
This section walks you through a practical example that demonstrates how Hibernate’s dirty checking mechanism works. We’ll create a simple Employee
entity, configure Hibernate, and then write a Java program that updates the entity without explicitly calling an update method.
2.1 Java Entity
The Employee
class is a basic JPA entity mapped to the employees
table. It includes fields for id
, name
, and salary
. The @Entity
and @Table
annotations tell Hibernate to treat this as a persistent class.
import jakarta.persistence.*; @Entity @Table(name = "employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double salary; // Constructors public Employee() {} public Employee(String name, double salary) { this.name = name; this.salary = salary; } // Getters and setters public Long getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } }
When this entity is managed by a Hibernate session, any changes made to its fields (e.g., updating the salary) will be tracked automatically through dirty checking.
2.2 Hibernate Configuration file
The configuration file hibernate.cfg.xml
contains the database connection properties and mapping information. Here we are connecting to a local MySQL database and telling Hibernate to automatically update the schema if necessary.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatedb</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">password</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.hbm2ddl.auto">update</property> <property name="show_sql">true</property> <mapping class="Employee"/> </session-factory> </hibernate-configuration>
The hibernate.hbm2ddl.auto=update
setting allows Hibernate to create or update the table schema based on your entity. The show_sql=true
property ensures SQL queries are logged to the console for visibility.
2.3 Main Code to Demonstrate Dirty Checking
Now let’s write the core Java program that loads an Employee
record, modifies its salary, and commits the transaction. Hibernate will track the change automatically and update the database during the commit.
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class DirtyCheckDemo { public static void main(String[] args) { SessionFactory factory = new Configuration().configure().buildSessionFactory(); Session session = factory.openSession(); session.beginTransaction(); // Load employee from DB Employee emp = session.get(Employee.class, 1L); if (emp != null) { System.out.println("Original Salary: " + emp.getSalary()); // Update the salary emp.setSalary(emp.getSalary() + 1000); // Dirty Checking will detect this System.out.println("Updated Salary: " + emp.getSalary()); } // No need to call session.update() session.getTransaction().commit(); // Hibernate will auto-detect the change session.close(); factory.close(); } }
Notice how there is no call to session.update(emp)
. Hibernate automatically detects the change to the salary
field and executes an UPDATE
SQL statement during commit()
due to dirty checking.
2.4 Output
This is the console output showing the original and updated salary values, followed by the SQL statements generated by Hibernate:
Original Salary: 5000.0 Updated Salary: 6000.0 Hibernate: select employee0_.id as id1_0_0_, employee0_.name as name2_0_0_, employee0_.salary as salary3_0_0_ from employees employee0_ where employee0_.id=? Hibernate: update employees set salary=? where id=?
The select
query loads the employee record, and the update
query is automatically generated when Hibernate detects that the salary
field was modified during the transaction.
3. How to Disable the Dirty Checking Mechanism?
In some cases, you may want to prevent Hibernate from tracking changes to an entity during a session. This can be useful when:
- You want to read data without the risk of unintentionally persisting changes.
- You are working with legacy data where changes should not be persisted.
- You want fine-grained control over when updates happen.
Here are a few common ways to disable or bypass the dirty checking mechanism in Hibernate.
3.1 Using session.evict() to Detach an Entity
The session.evict()
method removes the entity from the persistence context. Once detached, Hibernate no longer tracks changes made to that object, and no automatic update will be triggered on transaction commit.
Session session = factory.openSession(); session.beginTransaction(); Employee emp = session.get(Employee.class, 1L); session.evict(emp); // Detach the entity from the session emp.setSalary(7000); // This change will NOT be tracked or persisted session.getTransaction().commit(); // No SQL UPDATE is triggered session.close();
You can reattach a detached object later using session.update()
if needed.
3.2 Setting the Entity as Read-Only
You can also mark an entity as read-only, which tells Hibernate not to track or persist any changes made to it, even though it remains managed by the session.
Session session = factory.openSession(); session.beginTransaction(); Employee emp = session.get(Employee.class, 1L); session.setReadOnly(emp, true); // Mark as read-only emp.setSalary(9000); // Change will be ignored session.getTransaction().commit(); // No UPDATE statement will be triggered session.close();
Setting an entity as read-only is often safer than evicting it, especially if you still want to use lazy-loaded associations or ensure the entity remains part of the session context.
3.3 Executing a Read-Only Transaction
If you’re only interested in fetching data and not making any changes, you can declare the entire transaction as read-only using the JPA or Spring annotations (if you’re using Spring framework).
@Transactional(readOnly = true) public void readOnlyOperation() { Session session = sessionFactory.getCurrentSession(); Employee emp = session.get(Employee.class, 1L); emp.setSalary(8000); // This change is ignored during a read-only transaction }
This approach is more declarative and useful in layered applications like Spring Boot.
3.4 Manual Flush Control (Advanced)
Another technique is to turn off automatic flushing and take control of when Hibernate syncs with the database. While this does not strictly disable dirty checking, it allows you to bypass automatic persistence in some scenarios:
session.setFlushMode(FlushMode.MANUAL); // Only flush when explicitly told Employee emp = session.get(Employee.class, 1L); emp.setSalary(7500); // Change tracked, but won't be persisted session.getTransaction().commit(); // No flush = No UPDATE // Use session.flush() to manually trigger DB sync if needed
Use manual flush mode carefully, as forgetting to flush changes may lead to data inconsistency.
4. Conclusion
Hibernate’s dirty checking is a powerful and convenient feature that helps in reducing boilerplate code and managing database updates seamlessly. It ensures that only modified entities are updated, improving performance. However, developers should also understand how to control it when needed to avoid unexpected database changes.