views:

230

answers:

3

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?

+4  A: 
  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.
Bozho
Thanks, will reread spring doc about this topic!
abovesun
It not always make sense to mark Service method as transactional, I have 2 pluggable persistent provider in my app one is JPA+Hibernate, other is MongoDB+custom DAO, and probably will be a third one - Cassandra support. So @Transactional only make sense in case when JPA active.
abovesun
not quite - transactional behaviour is a cross-persistence-mechanism concept. True, JPA demands transactions, while other mechanisms don't, but transactions still exist. And with a proper transaction manager, it makes sense to use the annotation at service level.
Bozho
+2  A: 

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.

tkr
thanks for such deep explanation, by the way, what if I'll activate aspectj as I know aspectj is alternative to dynamic proxy.it is not make sense to mark createSuperuser as transactional, as long I'm still invoke it from inside of getSuperUser at the same class, but yes - when I mark getSuperUser transactional - everything works fine
abovesun
A: 

Your problem is related to the limitations of Spring AOP. Bozho's answer is a good one and you should consider refactor your code to support his advices.

But if you want your transaction to work without changing any code, it's possible!

Spring AOP is the default choice in the Spring aspects technologies. But with some configuration and adding AspectJ weaving, it will work, since AspectJ is a much more powerful technology that enables pointcuts between two methods in the same class.

Espen
thanks Espen, you answered on my comment for previous to yours answer
abovesun