This article describes how transactions are handled and managed in Hibernate Framework. We will also look into an EntityManager interface to see how can it be helpful in ensuring data consistency and eliminating unnecessary roundtrips to the database. Take a look at previous article to see what transactions are and what potential problems you may come across when they run in parallel.
It’s important to note that any data creation or modification requires a database transaction. Hibernate will throw an exception when it detects that you’re trying to modify data without a transaction. Understanding how transactions work in the popular ORM framework is vital then.

Default Hibernate Transaction Isolation Level

Similarly as JDBC, Hibernate uses a transactional mechanism provided by the database vendor. Default Hibernate’s transaction isolation level, therefore, will be the one the database vendor provides. For majority of databases it’s Read Committed. For MySql its Repeatable Read.
Hibernate offers an additional level of protection through its EntityManager implementation. Read on to see how it works.

Starting a Transaction in Hibernate

Here’s how you start a transaction when using a plain Java and Hibernate application.


Car car = new Car("Volvo");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();

entityManager.persist(car);

transaction.commit();
entityManager.close();

Starting a Transaction with Hibernate and Spring

@Transactional annotation tells Spring’s TransactionManager that a persistCar method needs to run in transactional context.


@Transactional
public void persistCar(Car car) {    
    entityManager.persist(car);
}

Altering transaction isolation level

The EntityTransaction, which we saw above does not offer a possibility to alter transaction isolation directly. Because Hibernate uses JDBC in the background, there is an option to use JDBC’s Connection.setTransactionIsolation method. Official Connection interface documentation states that this method only “Attempts to change the transaction isolation level for this Connection object to the one given” and that “If this method is called during a transaction, the result is implementation-defined”.
This is how you can attempt to set transaction isolation with JDBC when using Hibernate.

This code changes the name column value for each Car with named Golf in Serializable transaction.


public class CarNameWork implements org.hibernate.jdbc.Work {

    private String nameToChange;
    private String newName;

    public CarNameWork(String nameToChange, String newName) {

        this.nameToChange = nameToChange;
        this.newName = newName;
    }

    @Override
    public void execute(Connection connection) throws SQLException {
        connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

        try(PreparedStatement updateStmt = connection.prepareStatement("UPDATE CAR SET name=? WHERE name=?")) {
            updateStmt.setString(1, newName);
            updateStmt.setString(2, nameToChange);
            int updatedRows = updateStmt.executeUpdate();
            System.out.println(String.format("Changed name to %s records", updatedRows));
        }
    }
}

Here’s how to execute a Work item.


EntityManager entityManager = createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
Session session = entityManager.unwrap(Session.class);
session.doWork(new CarNameWork("Golf", "Volkswagen Golf"));
transaction.commit();
entityManager.close();

Altering Spring’s default transaction isolation

Altering the isolation level in Spring requires setting the property in a @Transactional annotation.


@Transactional(isolation = Isolation.REPEATABLE_READ)
public void persistCar(Car car) {    
    entityManager.persist(car);
}

Hibernate’s Persistence Context

On top of transaction support, Hibernate offers an additional level of protection through it’s Persistence Context.
Persistence Context is created whenever we create an EntityManager instance. It’s a form of cache for entities in persistent state. Whenever you run an EntityManager.find() or persist(), entities will be stored in entity manager cache. They will stay there until the context is flushed and cleaned.
Flushing is a process of writing changes from the cache, back to the database. Hibernate will attempt to populate changes to the database as late as possible so that the transaction duration is as short as possible.
Hibernate will flush the persistence context in the following situations:

  • When the transaction is committed
  • When a query is executed
  • When EntityManager.flush() is called

This effectively means that within boundaries of the same transaction, requests to EntityManager to find the same entity will be very quickly retrieved from cache, provided entity is already in persistent state. Multiple requests for the same entity will hit the database only once within single transaction.
The above is especially important when using Spring’s declarative transaction approach, where a single transaction can span several service calls and where transaction boundaries may not be obviously visible.
Because Persistence Context caches its entities, no other transaction will be able to modify data residing in application memory. This mechanism brings all the goodness of Repeatable Read transaction isolation. This also works for databases with lower transactional isolation level.

To understand more about transactions, take a look here:

Leave a Comment