为什么在Data JPA中进行查询时必须使用@Modifying注解

81

例如,我在我的CRUD接口中有一个方法,该方法从数据库中删除用户:

public interface CrudUserRepository extends JpaRepository<User, Integer> {

    @Transactional
    @Modifying
    @Query("DELETE FROM User u WHERE u.id=:id")
    int delete(@Param("id") int id, @Param("userId") int userId);
}

这种方法只能在使用@Modifying注解时起作用。但是,为什么需要在这里使用该注解?为什么Spring不能分析查询并理解它是修改查询呢?


23
@Repository 中的 @Transactional 注解是不良实践,最好在 Service 中使用它。因为一个业务操作(标记为事务)可能由多个对 DB 的请求组成,甚至可能由多个 DAO 发起。更多信息请参见:https://dev59.com/EnNA5IYBdhLWcg3wIqPr - Dan Brandt
1
@DanBrandt,除非您有自定义存储库实现和自定义方法,需要将多个查询作为一个事务执行(例如计算所有记录、选择10个ID、按ID选择记录,并在1个对象中返回所有这些数据)。 - Bojan Vukasovic
4个回答

85
注意! 使用@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); // store in first level cache
        johnUser.setActive(false);
        repo.save(johnUser);

        repo.deleteInActiveUsers();// BAM it won't delete JOHN right away
        
        // JOHN still exist since john with active being false was not 
        // flushed into the database when @Modifying kicks in
        // so imagine if after `deleteInActiveUsers` line you called a native 
        // query or started a new transaction, both cases john 
        // was not deleted so it can lead to faulty business logic 
    }

场景2为什么要使用clearAutomatically
在下面的考虑中,假设johnUser.active已经是false了。
service {
       User johnUser = userRepo.findById(1); // store in first level cache
       repo.deleteInActiveUsers(); // you think that john is deleted now 
       System.out.println(userRepo.findById(1).isPresent()) // TRUE!!!
       System.out.println(userRepo.count()) // 1 !!!
       
       // JOHN still exists since in this transaction persistence context
       // John's object was not cleared upon @Modifying query execution, 
       // John's object will still be fetched from 1st level cache 
       // `clearAutomatically` takes care of doing the 
       // clear part on the objects being modified for current 
       // transaction persistence context
}

所以,如果在同一事务中,在执行@Modifying的代码行之前或之后,你对修改过的对象进行了操作,那么你需要使用clearAutomaticallyflushAutomatically,否则你可以跳过使用这些标志。
顺便说一下,这也是为什么你应该始终在服务层上加上@Transactional注解的另一个原因,这样你在同一事务中只能有一个持久化上下文来管理所有的实体。 由于持久化上下文与Hibernate会话绑定,你需要知道一个会话可以包含多个事务,请参考这个答案了解更多信息https://dev59.com/92035IYBdhLWcg3wc_sm#5409180 Spring Data的工作方式是将事务连接在一起(称为事务传播),形成一个事务(默认传播(REQUIRED)),请参考这个答案了解更多信息https://dev59.com/ql8e5IYBdhLWcg3wd6Q6#25710391 如果您有多个孤立的事务(例如在服务上没有事务注释),则可以使用连接来连接这些事务,因此您将按照Spring Data的方式进行多个会话,因此您将拥有多个持久性上下文(也称为一级缓存),这意味着您可能会在持久性上下文中删除/修改实体,即使使用了flushAutomatically,同样被删除/修改的实体可能已经在另一个事务的持久性上下文中被获取和缓存,这将导致错误的业务决策,因为数据不正确或未同步。

52

这将触发被注解为更新查询而不是选择查询的方法。由于执行修改查询后,EntityManager 可能包含过时的实体,我们会自动清除它(有关详细信息,请参阅 EntityManager.clear() 的 JavaDoc)。这将有效地删除 EntityManager 中仍未刷新的所有未决更改。如果您不希望自动清除 EntityManager,则可以将 @Modifying 注释的 clearAutomatically 属性设置为 false;

有关更多详细信息,请访问以下链接:

http://docs.spring.io/spring-data/jpa/docs/1.3.4.RELEASE/reference/html/jpa.repositories.html


在最近的 JPA 版本中,清除自动标志(clearAutomatically)和刷新自动标志(flushAutomatically)默认都被设置为 false。因此,如果想要清除或刷新,则必须将标志设置为 true。 - justMe
2
@justMe,你的意思是说,在最近的版本中,如果我们只包含修改注释而没有任何属性,比如flush或clear,那么这个注释就是无用的,什么也不做了吗? - theprogrammer
2
@theprogrammer 我想说的是这些标志默认设置为false,文档在这里https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/Modifying.html,如果你不设置它们会发生什么?请看下面Younas的答案或者官方文档https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.modifying-queries。 - justMe

13

需要使用@Modifying注释的查询包括INSERT、UPDATE、DELETE和DDL语句。

添加@Modifying注释表示该查询不是用于SELECT查询。


1

当您只使用@Query注释时,您应该使用选择查询。 但是如果您使用@Modifying注释,则可以在方法上使用插入、删除和更新查询。


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