开发者

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 n开发者_StackOverflow社区ew 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?


  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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜