为什么我们必须在Data Jpa中使用@Modifying注释进行查询

例如,我在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 不能分析查询并理解它是一个修改查询?


答案 1

谨慎!

使用将删除持久性上下文中托管实体上的任何挂起的更新 spring 状态如下:@Modifying(clearAutomatically=true)

这样做会触发对方法进行批注的查询,将其作为更新查询而不是选择查询。由于在执行修改查询后,EntityManager 可能包含过时的实体,因此我们不会自动清除它(有关详细信息,请参阅 EntityManager.clear() 的 JavaDoc),因为这会有效地删除 EntityManager 中仍挂起的所有未刷新的更改。如果希望自动清除 EntityManager,则可以将@Modifying注释的 clearAutomatic 属性设置为 true。

幸运的是,从 Spring Data 添加标志 (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#flushAutomaticallySpring Boot 2.0.4.RELEASEflushAutomatically

所以最安全的使用方法是:@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
        
        // JOHN still exist since john with active being false was not 
        // flushed into the database when @Modifying kicks in
    }

场景 2 为什么清除自动 在下面考虑 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 exist 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
}

因此,如果 - 在同一事务中 - 您正在玩修改后的对象,那么使用&如果不是,那么您可以使用这些标志跳过@ModifyingclearAutomaticallyflushAutomatically

顺便说一句,这是您应该始终在服务层上放置注释的另一个原因,以便您只能为同一事务中的所有托管实体提供一个持久性上下文。由于持久性上下文仅限于休眠会话,因此您需要知道一个会话可以包含几个事务,请参阅此答案以获取更多信息 https://stackoverflow.com/a/5409180/1460591 Spring数据的工作方式是它将事务一起(也称为事务隔离)联接到一个事务中(默认隔离(必需))请参阅此答案以获取更多信息 https://stackoverflow.com/a/25710391/1460591@Transactional

如果您有多个事务(例如,在服务上没有事务注释),则可以将事物连接在一起,因此您将按照spring数据的工作方式进行多个会话,因此您有多个持久性上下文,这意味着即使使用相同的已删除/修改元素,您也可以删除/修改持久性上下文中的元素,可能会在另一个事务的持久性上下文中获取和缓存,这将导致由于错误而导致的业务错误决策或未同步的数据flushAutomatically


答案 2

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

有关更多详细信息,您可以点击此链接:-

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


推荐