Enterprise Java

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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button