我在相对简单的用例中发现了非常奇怪的行为,可能由于没有对@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。有什么想法吗?
答案 0 :(得分:9)
@Transactional
仅用于来自对象外部的调用。对于内线电话,它不是。要解决此问题,只需将@Transactional
添加到您的入口点。@Transactional
用于DAO - 而是在服务类上使用它。答案 1 :(得分:7)
我认为带有注释的声明性事务是在代理的基础上实现的。
如果您通过动态代理访问DAO,它会检查是否有注释并用事务包装它。
如果你从班级内部打电话给你的班级,就无法拦截这个电话。
为避免此问题,您也可以使用注释标记createSuperuser方法。
答案 2 :(得分:0)
您的问题与Spring AOP的限制有关。 Bozho的answer很好,您应该考虑重构代码以支持他的建议。
但如果您希望您的交易在不更改任何代码的情况下工作,那么这是可能的!
Spring AOP是Spring方面技术的默认选择。但是通过一些配置和添加AspectJ编织,它将起作用,因为AspectJ是一种功能更强大的技术,可以在同一个类中的两个方法之间实现切入。