Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange befaviour of spring transaction support for JPA + Hibernate +@Transactional annotation

I found out really strange behavior on relatively simple use case, probably I can't understand it because of not deep knowledges of spring @Transactional nature, but this is quite interesting.

I have simple User dao that extends spring JpaDaoSupport class and contains standard save method:

@Transactional
public User save(User user) {
    getJpaTemplate().persist(user);
    return user;
}

If was working fine until I've add new method to same class: User getSuperUser(), this method should return user with isAdmin == true, and if there is no super user in db, method should create one. Thats how it was looking like:

 public User createSuperUser() {
    User admin = null;

    try {
        admin = (User) getJpaTemplate().execute(new JpaCallback() {
            public Object doInJpa(EntityManager em) throws PersistenceException {
                return em.createQuery("select u from UserImpl u where u.admin = true").getSingleResult();
            }
        });
    } catch (EmptyResultDataAccessException ex) {
        User admin = new User('login', 'password');
        admin.setAdmin(true);
        save(admin); // THIS IS THE POINT WHERE STRANGE THING COMING OUT
    }

    return admin;
}

As you see code is strange forward and I was very confused when found out that no transaction was created and committed on invocation of save(admin) method and no new user wasn't actually created despite @Transactional annotation.

In result we have situation: when save() method invokes from outside of UserDAO class - @Transactional annotation counted and user successfully created, but if save() invokes from inside of other method of the same dao class - @Transactional annotation ignored.

Here how I was change save() method to force it always create transaction.

public User save(User user) {
    getJpaTemplate().execute(new JpaCallback() {
        public Object doInJpa(EntityManager em) throws PersistenceException {
            em.getTransaction().begin();
            em.persist(user);
            em.getTransaction().commit();
            return null;
        }
    });
    return user;
}

As you see I manually invoke begin and commit. Any ideas?

like image 689
Andriy Kopachevskyy Avatar asked May 26 '10 08:05

Andriy Kopachevskyy


People also ask

How does the annotation @transactional change the behavior?

The @Transactional annotation makes use of the attributes rollbackFor or rollbackForClassName to rollback the transactions, and the attributes noRollbackFor or noRollbackForClassName to avoid rollback on listed exceptions. The default rollback behavior in the declarative approach will rollback on runtime exceptions.

What is the use of @transactional annotation in Spring boot?

So when you annotate a method with @Transactional , Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

What are the transactions supported by Spring transaction module?

Spring supports both programmatic and declarative transaction management.

What happens if a method annotated with @transactional calls another method annotated with transactional?

If you call a method with a @Transactional annotation from a method with @Transactional within the same instance, then the called methods transactional behavior will not have any impact on the transaction.

What does the @transactional annotation mean?

The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics; for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction".

Is it sufficient to annotate the classes with the @transactional annotation?

It is not sufficient to tell you simply to annotate your classes with the @Transactional annotation, add the line ( <tx:annotation-driven/> ) to your configuration, and then expect you to understand how it all works.


2 Answers

  1. @Transactional is taken into account only for calls from outside the object. For inside calls it isn't. To work this around just add @Transactional to your entry point.
  2. Don't use @Transactional for your DAO - use it on the Service classes instead.
like image 93
Bozho Avatar answered Nov 14 '22 02:11

Bozho


I think declarative transactions with annotation is implemented on base of proxies.

If you access your DAO through the dynamic proxy it checks whether there is an annotation and wraps it with a transaction.

If you call your class from inside the class there is no way to intercept this call.

To avoid the problem you could mark the createSuperuser method with an annotation too.

like image 40
tkr Avatar answered Nov 14 '22 03:11

tkr