Understanding dirty checking in Hibernate.
Hibernate provides an automatic dirty checking mechanism that is used to check object’s fields have been modified or not in the same transaction. If some fields have been modified then hibernate will automatically execute SQL UPDATE statement once the session is flushed or the transaction is committed. The benefit of dirty checking is we don’t need to call session.save() explicitly and if we try to retrieve an entity later then we will get updated data. In this post, we are going to discuss Dirty checking in Hibernate in depth.
To understand Hibernate dirty checking, Let’s see an example. Consider we have an entity called Book that contains fields id, name, noOfPages, price, and author and also, we have one book record in the Database.
@Entity
@DynamicUpdate
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "no_of_page")
private Integer noOfPages;
@Column(name = "price")
private String price;
@Column(name = "author")
private String author;
//getter-setter
}
First, we retrieve one record using session.get() and update any field(for example name).
public class SpringMain {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
Long id = 1l;
Book book = session.get(Book.class, id); //line no - 9
book.setName("Alchemist"); //line no - 10
transaction.commit(); //line -11
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
We are retrieving the Book entity using session.get() i.e line number -9. We are updating Book’s name using line number 10. Once line number 11 i.e session.commit() will execute, hibernate will create the below update query.
We can see, the automatically update SQL Statement has been executed by hibernate which is called Hibernate Dirty checking.
How Hibernate Performs Dirty Checking
Hibernate’s dirty checking mechanism automatically tracks changes that have been made in entities and ensures that only the modified entities are updated in the database during the flush or transaction commit phase. This is primarily managed by the session (or the persistence context), which keeps track of all entity instances that it retrieves or saves.
Let’s see how Hibernate dirty checking process works internally.
- Session and Entity Tracking: When you retrieve an entity using a Hibernate session, Hibernate keeps a snapshot of the entity’s original state (the state when it was retrieved from the database). This snapshot is essentially a copy of the entity’s properties at the time it was loaded into the session.
- Modifications to the Entity: As you make changes to the entity’s properties within the bounds of a transaction, these changes are held in memory but are not immediately persisted to the database.
- Flush Time: When the session is flushed (either manually by calling
session.flush()
or automatically at transaction commit), Hibernate checks the current state of the entity against the original snapshot. - Dirty Checking: Hibernate compares each property of the entity with its corresponding value in the original snapshot. If any property has changed, the entity is marked as “dirty.”
- SQL Update Execution: For each dirty entity, Hibernate generates and executes an SQL
UPDATE
statement that updates only the changed properties in the database.
Before going forward let’s get some basic about @DynamicUpdate annotation. Hibernate provides @DynamicUpdate annotation that is used with class. If we use @DynamicUpdate annotation with entity then SQL UPDATE statement will execute only for the field that has been modified i.e hibernate includes only those columns that need to update in the SQL UPDATE statement. For example, In the above screen shot did you notice an update statement is executed for name filed(since we are using @DynamicUpdate with Book entity). This might result in some performance improvement.
If we don’t use @DynamicUpdte annotation with an entity, even if we update a single field, hibernate SQL UPDATE statement executes for all fields.
First, we will save a Book entity, and then we will update a field i.e name.
public class SpringMain {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
// First save book into database
Book book = new Book();
book.setName("Rich Dad Poor Dad");
book.setAuthor("Robert Kiyosaki");
book.setNoOfPages(400);
book.setPrice("500");
session.save(book);
// Update Book's name field
book.setName("Alchemist");
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
The query is generated if we don’t use @DynamicUpdate with the Book entity.
Let’s use @DynamicUpdate annotation with the Book entity.
@Entity
@DynamicUpdate
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "no_of_page")
private Integer noOfPages;
@Column(name = "price")
private String price;
@Column(name = "author")
private String author;
//getter-setter
}
The query generated while using @DynamicUpdate with the Book entity.
Hibernate dirty checking example
Let’s see hibernate dirty checking example using Oracle from scratch.
create maven project, Don’t forget to check ‘Create a simple project (skip)’ click on next. Fill all details(GroupId – hibernatedirtychecking, ArtifactId – hibernatedirtychecking, and name – hibernatedirtychecking) and click on finish. Keep packaging as the jar.
Add maven dependency.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>hibernategetvsload</groupId>
<artifactId>hibernategetvsload</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hibernategetvsload</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
Directory structure.
Define entity class.
package com.javatute.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.DynamicUpdate;
@Entity
@DynamicUpdate
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "no_of_page")
private Integer noOfPages;
@Column(name = "price")
private String price;
@Column(name = "author")
private String author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getNoOfPages() {
return noOfPages;
}
public void setNoOfPages(Integer noOfPages) {
this.noOfPages = noOfPages;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
hibernate.cfg.xml
<!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="connection.driver_class">oracle.jdbc.driver.OracleDriver
</property>
<property name="connection.url">jdbc:oracle:thin:@localhost:1521:XE</property>
<property name="connection.username">SYSTEM</property>
<property name="connection.password">oracle1</property>
<property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">create</property>
<mapping class="com.javatute.entity.Book" />
</session-factory>
</hibernate-configuration>
See more about hibernate.cfg.xml file in detail.
Define main class.
package com.javatute.main;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import com.javatute.entity.Book;
public class SpringMain {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
// First save book into database
Book book = new Book();
book.setName("Rich Dad Poor Dad");
book.setAuthor("Robert Kiyosaki");
book.setNoOfPages(400);
book.setPrice("500");
session.save(book);
// Update Book's name field
//book.setName("Alchemist");
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
Note – In this example, we are using Oracle as a database. Download Oracle driver jar file from this link, else you may get Unable to load class [oracle.jdbc.driver.OracleDriver] exception. Once you download OracleDriver jar, right-click on project -> properties -> java build path -> libraries -> add external jars.
Run the application.
How to check entity is dirty in Hibernate.
Session interface contains isDirty() method in order to check entity is dirty or not. See isDirty() docs here.
session.isDirty()
Let’s see below code snippet.
public class SpringMain {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
System.out.println("Before updating the field--->" + session.isDirty());
Book book = session.get(Book.class, 21l);
book.setName("Alchemist2");
System.out.println("After updating the field--->" + session.isDirty());
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
How to avoid/disable dirty checking in hibernate.
Sometime you may want to disable hibernate auto dirty checking feature in some part of code within transaction. We can use session.setHibernateFlushMode(FlushMode.MANUAL) method. In this case, untill we will not call session.flush() explicitly, update query will not execute.
public class SpringMain {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
Book book = session.get(Book.class, 21l);
session.setHibernateFlushMode(FlushMode.MANUAL);
book.setName("Alchemist3");
session.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
If we remove session.flush(); from above code update query will not get executed only select query will get executed.
Hibernate Dirty checking for collections/child entities.
For association mapping(OneToMany, OneTOne) hibernate will also perform auto dirty checking. Suppose we have two entities Book.java and Story.java in an OneToOne unidirectional relationship.
Book.java
@Entity
@DynamicUpdate
public class Book implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "no_of_page")
private Integer noOfPages;
@Column(name = "price")
private String price;
@Column(name = "author")
private String author;
@Column(name = "version")
@Version
private Long version;
@OneToMany(cascade = CascadeType.ALL, fetch= FetchType.LAZY)
@JoinColumn(name = "book_id",referencedColumnName="id")
private List<Story> storyList = new ArrayList<>();
}
Story.java
@Entity
@DynamicUpdate
public class Story implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "version")
@Version
private Long version;
}
public class SpringMain {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
Story s1 = new Story();
s1.setName("just_story");
List<Story> storyList = new ArrayList<>();
Book b1= new Book();
b1.setName("cccc");
b1.setAuthor("tuu");
b1.setPrice("55");
b1.setPrice("888");
storyList.add(s1);
b1.setStoryList(storyList);
session.save(b1);
s1.setName("child_dirty_check");
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
We perform a save operation for Book that contains a story. Later we update the Story’s name field. Hibernate will fire update query for the story as below.
How dirty checking works internally
Hibernate dirty checking internally works on inspection. Hibernate performs an inspection of all persistent objects associated with a session when the session is flushed. It compares an object’s property values at the end of a transaction to a saved snapshot of the state of the object when it was loaded from the database.
Note – See more about Hibernate Transaction Management.
To understand how dirty check works internally, consider we have one record in the database with id 1. First we will retrieve the Book entity for this id using seesion.get() method then we will update some fields (for example name).
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
try {
Long id = 1l;
Book book = session.get(Book.class, id);
book.setName("Alchemist");//line no - 8
transaction.commit();//line no - 9
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
Observed the above code. In line number 8 we are updating Book’s name. Because of dirty checking, even we don’t do session.save(book) explicitly, hibernate will fire update statement once line number 9(i.e transaction.commit()) will execute.
Let’s see what happens internally when transaction.commit() executes.
- The transaction.commits() will internally call session.beforeTransactionCompletion(). Further session.flushBeforeTransactionCompletion() will get invoked and it will call managedFlush().
public void flushBeforeTransactionCompletion() {
//some more code
try {
if ( flush ) {
managedFlush();
}
}
catch (RuntimeException e) {
//some more code
}
}
- The mangedFlush() method will call doFlush() method that will further call listener.onFlush() i.e DefaultFlushEventListener.onFlush().
for ( FlushEventListener listener : listeners( EventType.FLUSH ) ) {
listener.onFlush( flushEvent );
}
- The onFlush() will further call flushEverythingToExecutions().
public void onFlush(FlushEvent event) throws HibernateException {
try {
flushEverythingToExecutions( event );
}
finally {
//some more code
}
}
- The flushEverythingToExecutions() will call flushEntities() method that further calls listener.onFlushEntity( entityEvent ). The onFlushEntity() method further calls isUpdateNecessary() method.
public void onFlushEntity(FlushEntityEvent event) throws HibernateException {
if ( isUpdateNecessary( ) ) {
//more code
}
}
- Inside isUpdateNecessary(), dirtyCheck() method is defined that is calling findDirty() method.
protected void dirtyCheck(final FlushEntityEvent event) throws HibernateException {
//more code
dirtyProperties = persister.findDirty();
event.setDirtyProperties( dirtyProperties );
}
- The findDirty() method determine if any of the given field values are dirty and rerurns int array.
public static int[] findDirty(
) {
for ( int i = 0; i < span; i++ ) {
//some logic to find dirty field - Check source code for more details org.hibernate.type.TypeHelper.java
}
}
Now hibernate knows what fields are dirty and it will fire update query for those fields.
That’s all about Dirty Checking in Hibernate.
Other Hibernate tutorial.
- Hibernate Eager vs Lazy loading Example.
- JPA Cascade Types example using Spring Boot.
- JPA CriteriaBuilder example.
- Hibernate Query Language example
- Difference between save() and persist() in Hibernate
- Difference Between get() and load() in Hibernate
- JPA and Hibernate Cascade Types example with Spring Boot
- @ElementCollection Example in Hibernate/JPA Using Spring Boot
- Hibernate First Level Cache example using Spring Boot
- @OrderBy Annotation in Hibernate for Sorting
- @Version Annotation Example In Hibernate
- Hibernate Validator Constraints Example Using Spring Boot
- Hibernate Table Per Concrete Class Spring Boot
- Hibernate Table Per Subclass Inheritance Spring Boot
- Hibernate Single Table Inheritance using Spring Boot
- Many To Many Mapping In Hibernate/JPA Using Spring Boot And Oracle
- One To Many Bidirectional Mapping In Hibernate/JPA Annotation Example Using Spring Boot and Oracle
- Many To One Unidirectional Mapping In Hibernate/JPA Annotation Example Using Spring Boot and Oracle
- @Temporal Annotation Example In Hibernate/Jpa Using Spring Boot
Download source code from github.
See Hibernate Dirty Checking Docs.
Summary – Hibernate Dirty Checking mechanism automatically executes update statement if some field has been modified and performs update operation for that column only.