【问题标题】:Strange befaviour of spring transaction support for JPA + Hibernate +@Transactional annotationJPA + Hibernate +@Transactional注解的spring事务支持的奇怪行为
【发布时间】:2011-02-24 01:39:49
【问题描述】:

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

我有一个简单的用户 dao,它扩展了 spring JpaDaoSupport 类并包含标准保存方法:

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

如您所见,我手动调用了开始和提交。有什么想法吗?

【问题讨论】:

    标签: java hibernate spring jpa aop


    【解决方案1】:

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

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

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

    【讨论】:

    • 感谢 Espen,您在我的评论中回答了您之前的回答
    【解决方案2】:

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

    如果您通过动态代理访问您的 DAO,它会检查是否存在注释并将其与事务包装在一起。

    如果您从类内部调用您的类,则无法拦截此调用。

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

    【讨论】:

    • 感谢您提供如此深入的解释,顺便说一句,如果我将激活 aspectj 会怎样,因为我知道 aspectj 是动态代理的替代方案。将 createSuperuser 标记为事务性是没有意义的,只要我仍然从同一类的 getSuperUser 内部调用它,但是是的 - 当我将 getSuperUser 标记为事务性时 - 一切正常
    【解决方案3】:
    1. @Transactional 仅适用于来自对象外部的调用。对于内部呼叫,它不是。要解决此问题,只需将 @Transactional 添加到您的入口点即可。
    2. 不要将@Transactional 用于您的 DAO - 而是在服务类上使用它。

    【讨论】:

    • 将服务方法标记为事务性并不总是有意义的,我的应用程序中有 2 个可插入的持久提供程序,一个是 JPA+Hibernate,另一个是 MongoDB+自定义 DAO,并且可能会是第三个 -卡桑德拉支持。因此,@Transactional 仅在 JPA 处于活动状态时才有意义。
    • 不完全——事务行为是一个跨持久性机制的概念。诚然,JPA 需要事务,而其他机制则不需要,但事务仍然存在。并且使用适当的事务管理器,在服务级别使用注释是有意义的。
    猜你喜欢
    • 1970-01-01
    • 2014-12-24
    • 1970-01-01
    • 1970-01-01
    • 2018-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多