对JPA + Hibernate + @Transactional注释的Spring事务支持的奇怪befaviour

时间:2010-05-26 08:22:59

标签: java hibernate spring jpa aop

我在相对简单的用例中发现了非常奇怪的行为,可能由于没有对@Transactional性质的深入了解而无法理解它,但这非常有趣。

我有简单的User dao,它扩展了spring JpaDaoSupport类并包含标准的save方法:

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

如果在我向同一个类添加新方法之前工作正常:用户getSuperUser(),此方法应返回isAdmin == true的用户,如果db中没有超级用户,则方法应创建一个。那是它的样子:

 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;
}

正如您所看到的,代码很奇怪,当发现在调用save(admin)方法时没有创建和提交任何事务时,我很困惑,尽管没有@Transactional注释,但实际上并没有创建新用户。

结果我们有这样的情况:当save()方法从UserDAO类外部调用时 - @Transactional注释计数并且用户成功创建,但是如果save()从同一个dao类的其他方法内部调用 - @Transactional注释忽略。

这里我是如何更改save()方法来强制它始终创建事务。

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;
}

如您所见,我手动调用begin和commit。有什么想法吗?

3 个答案:

答案 0 :(得分:9)

  1. @Transactional仅用于来自对象外部的调用。对于内线电话,它不是。要解决此问题,只需将@Transactional添加到您的入口点。
  2. 不要将@Transactional用于DAO - 而是在服务类上使用它。

答案 1 :(得分:7)

我认为带有注释的声明性事务是在代理的基础上实现的。

如果您通过动态代理访问DAO,它会检查是否有注释并用事务包装它。

如果你从班级内部打电话给你的班级,就无法拦截这个电话。

为避免此问题,您也可以使用注释标记createSuperuser方法。

答案 2 :(得分:0)

您的问题与Spring AOP的限制有关。 Bozho的answer很好,您应该考虑重构代码以支持他的建议。

但如果您希望您的交易在不更改任何代码的情况下工作,那么这是可能的!

Spring AOP是Spring方面技术的默认选择。但是通过一些配置和添加AspectJ编织,它将起作用,因为AspectJ是一种功能更强大的技术,可以在同一个类中的两个方法之间实现切入。