The detaching pitfall of declarative transaction management with Spring and JPA and Hibernate

Anyone who is familiar with Spring and JPA knows about the magical @Transactional annotation which helps us to focus on the business logic rather than programming transactions around.

This approach is so called as Declarative transaction management. If you have ever used SQL before, then you are familiar with the paradigm declarative, because the main focus here is to tell the machine “what to do” instead of telling “how to do” a certain thing.

Imagine when you are not using declarative transaction management, but rather a programmatic approach:

One would write:

EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try {
    tx = em.getTransaction();
    tx.begin();
    // do some work
    tx.commit();
} catch (RuntimeException e) {
    if ( tx != null && tx.isActive() ) 
        tx.rollback();
} finally {
    em.close();
}

See how much pain is that? You have to do this all the time when you are dealing with transactions. Using just a single annotation with a little bit of configuration – which you have to do only once – is much better.

Despite the fact that it’s really easy to use the declarative way, it’s bringing in some pitfalls which you should be aware of. One of these is that you have to understand what state transitions will happen to your entities in certain cases. To understand this, I’ll start with a little basics about entity state transitions in JPA.

State transitions in JPA

There are 4 entity states:

  • Managed
  • New
  • Removed
  • Detached

There are multiple ways to navigate between each other. For example if you call entityManager.find(id, clazz) you will get back a managed entity instance. If you call entityManager.remove(entity) , your entity will become deleted. I’ve attached a drawing about the states and some example methods, how you can go from one to another.

There are 2 states which we should really watch out for, managed and detached. A managed entity means that the JPA provider will record all the changes (managed by the JPA provider) and at transaction flushing time, the changes will be propagated to the database automatically. A detached entity means that the mentioned properties are not there, meaning that you can do anything with that entity instance, the changes won’t be recorded by the persistence provider and will not be propagated automatically to the database side.

As you can see on the picture above, you can call entityManager.detach(entity) or entityManager.clear()or entityManager.close()to make entities detached. Why is it important? Well, we’ll see in a couple of minutes.

@Transactional annotation in Spring

As I mentioned earlier in the article, the @Transactional annotation is basically tells Spring to wrap that particular code into a transactional context. What does this mean for you? It creates the transaction, in case of exception, the rollback mechanism starts working and if everything went right then the transaction will be committed. Of course there are lots of other things which are happening under the hood but maybe in another article I’ll cover some of those.

Additionally, you have the possibility to fine tune your unit of work per use case with the annotation but again, I might discuss this in a future article.

Let’s consider we have the following method on our Repository:

public Person load(int id) {
    return entityManager.load(id);
}

This is a really simple method, it only loads the entity by it’s id through the EntityManager. What we want to do in our application is to change one of the entities name to Steven. One would come up with the following solution, which is perfectly fine.

@Transactional
public void logic() {
    Person p = repository.load(1);
    p.setName(“Steven”);
}

What happens if we put the annotation onto our Repository?

@Transactional
public Person load(int id) {
    return entityManager.load(id);
}

And we have the same logic as before but without the @Transactional annotation, basically we swapped the annotations.

public void logic() {
    Person p = repository.load(1);
    p.setName(“Steven”);
    repository.update(p);
}

So what happens when you leave the transactional context and return an entity which is loaded in it? Spring closes the current PersistenceContext which causes all the loaded entities to become detached. The consequence of this that the changes will not be automatically recorded, so we have to use EntityManager#merge to propagate the changes. One would introduce a new method on the repository and mark it as @Transactional and inside it, calls merge:

@Transactional
public void update(Person p) {
    entityManager.merge(p); 
}

This is working, however there is one hidden issue here (at least with Hibernate) and important because of performance reasons.

Hibernate has a built-in dirty checking mechanism which takes an entity and decides whether an update statement is necessary or not. If you think about it, it’s pretty easy to do so, you have to check if the entity has any changes compared to the database state.

The merge operation automatically triggers this mechanism and the EntityManager which loaded the entity is now closed – making the entity detached – and in another transactional context we try to say, okay, please update this entity in the database if necessary. In order to do this, the entity MUST be loaded into the current PersistenceContext, so Hibernate can do the checking.

In this case the merge operation executes two SQL statements under the hood, one select to fetch the current state from the database and one update if anything changed. You can see this in the following logs:

Hibernate: 
    select
        person0_.id as id1_0_0_,
        person0_.name as name2_0_0_ 
    from
        persons person0_ 
    where
        person0_.id=?
Hibernate: 
    update
        persons 
    set
        name=? 
    where
        id=?

Imagine updating 1000 entities in this way, you are going to have 1000 selects and 1000 updates statements sent to the database, not the most efficient way, right?

Summary

Watch out for your transaction boundaries and be aware of your entity states because this problem is pretty hard to identify especially for someone who is not familiar with internal workings. At the end of the transaction, you can return detached entities because the underlying PersistenceContext will be closed. One way you can do this is to enable SQL logging when doing the testing and check the generated statements.

And don’t forget, JPA/Hibernate/Spring is not a silver bullet, they can do a lot of things for you at some cost, but if you are in control of them, you can decrease this cost considerably.

Feel free to reach me out in case of questions in the comments or on Twitter.

2 Replies to “The detaching pitfall of declarative transaction management with Spring and JPA and Hibernate”

  1. Michael says:
    1. Arnold Galovics says:

Leave a Reply to Arnold Galovics Cancel reply

Your email address will not be published. Required fields are marked *