注意!
使用
@Modifying(clearAutomatically=true)
会丢弃持久化上下文中管理的实体的任何待处理更新。Spring的文档中如下所述:
这样做会将方法注解的查询作为更新查询而不是选择查询来触发。由于在修改查询执行后,实体管理器可能包含过时的实体,我们不会自动清除它(有关详细信息,请参阅EntityManager.clear()的JavaDoc),因为这将有效地丢弃实体管理器中仍未刷新的所有非刷新更改。如果您希望实体管理器自动清除,请将@Modifying注解的clearAutomatically属性设置为true。
幸运的是,从Spring Boot 2.0.4.RELEASE开始,Spring Data添加了flushAutomatically标志(
https://jira.spring.io/browse/DATAJPA-806),以在执行修改查询之前自动刷新任何受管理的实体到持久化上下文。请参考
https://docs.spring.io/spring-data/jpa/docs/2.0.4.RELEASE/api/org/springframework/data/jpa/repository/Modifying.html#flushAutomatically。
因此,使用@Modifying的
最安全的方法是:
@Modifying(clearAutomatically=true, flushAutomatically=true)
如果我们不使用这两个标志,会发生什么情况?考虑以下代码:
repo {
@Modifying
@Query("delete User u where u.active=0")
public void deleteInActiveUsers();
}
场景1为什么要自动刷新
service {
User johnUser = userRepo.findById(1);
johnUser.setActive(false);
repo.save(johnUser);
repo.deleteInActiveUsers();
}
场景2为什么要使用clearAutomatically
在下面的考虑中,假设johnUser.active已经是false了。
service {
User johnUser = userRepo.findById(1);
repo.deleteInActiveUsers();
System.out.println(userRepo.findById(1).isPresent())
System.out.println(userRepo.count())
}
所以,如果在同一事务中,在执行
@Modifying
的代码行之前或之后,你对修改过的对象进行了操作,那么你需要使用
clearAutomatically
和
flushAutomatically
,否则你可以跳过使用这些标志。
顺便说一下,这也是为什么你应该始终在服务层上加上
@Transactional
注解的另一个原因,这样你在同一事务中只能有一个持久化上下文来管理所有的实体。
由于持久化上下文与Hibernate会话绑定,你需要知道一个会话可以包含多个事务,请参考这个答案了解更多信息
https://dev59.com/92035IYBdhLWcg3wc_sm#5409180
Spring Data的工作方式是将事务连接在一起(称为
事务传播
),形成一个事务(默认传播(REQUIRED)),请参考这个答案了解更多信息
https://dev59.com/ql8e5IYBdhLWcg3wd6Q6#25710391
如果您有多个孤立的事务(例如在服务上没有事务注释),则可以使用连接来连接这些事务,因此您将按照Spring Data的方式进行多个会话,因此您将拥有多个持久性上下文(也称为一级缓存),这意味着您可能会在持久性上下文中删除/修改实体,即使使用了
flushAutomatically
,同样被删除/修改的实体可能已经在另一个事务的持久性上下文中被获取和缓存,这将导致错误的业务决策,因为数据不正确或未同步。