Spring 事务支持 JPA 的奇怪行为+ Hibernate +@Transactional 注解

发布于 2024-09-03 04:22:42 字数 1642 浏览 2 评论 0原文

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

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

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

如果工作正常,直到我向同一类添加新方法:User getSuperUser(),此方法应该返回 isAdmin == true 的用户,如果有数据库中没有超级用户,方法应该创建一个。这就是它的样子:

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

别理我 2024-09-10 04:22:42
  1. @Transactional 仅适用于来自对象外部的调用。对于内部呼叫则不然。要解决此问题,只需将 @Transactional 添加到您的入口点即可。
  2. 不要对 DAO 使用 @Transactional - 而是在 Service 类上使用它。
  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.
↙温凉少女 2024-09-10 04:22:42

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

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

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

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

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.

执妄 2024-09-10 04:22:42

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文