为什么 @Transactional 可以自动保存到数据库?

34

我有一个被 @Transactional 注释的方法。 我从数据库中检索一个对象,更改一个字段,然后从该方法返回。 没有保存我的对象,但数据库仍会更新,这很奇怪。

请告诉我如何避免这种行为?


如果方法不是事务性的,会发生什么? - user1907906
我实际上需要 @Transactional 来避免 LazyInitializationException。 - chidar
2个回答

31

这是JPA的正常行为。

一旦您通过find()或其他方式检索到对象,该对象被视为已附加或属于持久性上下文。一旦退出方法,@Transactional触发Spring事务管理方面,将每个“脏”对象刷新到数据库并提交事务。由于您的对象已在持久性上下文和事务的上下文中进行了更改,因此即使不需要显式调用保存方法,更改也会保存到数据库中。

如果要更改对象而不影响数据库,则有两个选项:

  1. 在带有@Transactional注解的方法返回后更新字段
  2. 如果在方法中,请在实体管理器上调用detach

谢谢您的答案,我现在明白它是如何工作的了 ;) - chidar

30

这种行为是事务性的主要目的之一。

在事务方法即将返回之前,事务会提交,意味着对托管实体所做的所有更改都被刷新到数据库中。

如果发生错误,事务将被回滚,意味着不会向数据库提交任何更改。

当尝试访问惰性加载属性(可能是实体的集合)时,您可能会遇到LazyInitializationException。惰性加载属性在从数据库中提取实体时不会被实例化。

如果在事务中访问惰性加载属性,持久性提供程序将创建一个查询,实例化结果并将其附加到“父”实体。

编辑:如果您想要加载延迟属性,并且能够更改实体而不将更改持久化到数据库中,则可以使用fetch joins获取具有惰性属性的实体。

em.createQuery("SELECT e FROM MyEntity e JOIN FETCH e.lazyProp");

然后按照@orid描述的任一方法继续进行。

如果您没有使用fetch joins,则需要在事务内部访问延迟加载的属性:

myEntity.getLazyProp().size();
请注意调用 size() 方法。仅调用 getter 方法是不够的,因为您将得到一个代理。您需要执行需要来自属性的实际数据的操作。

1
说得非常好。你有关于这个主题的参考资料建议吗? - Kevin Bowersox
谢谢,Kevin。我唯一能推荐的资源是Pro JPA 2 Book,因为这是我用来学习JPA的。第6章,EntityManager / Transaction Management 可能是最好的查询位置。同样的知识也可能在Hibernate和Eclipselink文档中找到。 - kostja
感谢kosjta的回答,非常有用。我之前认为如果不调用update,数据就不会提交到数据库中。再次感谢 :) - chidar
不客气 :) 是的,容器管理的JPA在幕后做了一些不太明显的技巧。我真的很推荐我上面链接的《Pro JPA 2》书籍。 - kostja
1
你好kosjta, 我回来告诉你一件事。即使我使用了readOnly=true来避免LazyInitializationException,同时又不更新数据库,但实际上数据库仍然会被更新!有什么想法吗? - chidar
我不确定我理解了。您介意创建一个新问题吗?因为这超出了当前问题的范围。 - kostja

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接