为啥我们必须使用 @Modifying 注解在 Data Jpa 中进行查询

Posted

技术标签:

【中文标题】为啥我们必须使用 @Modifying 注解在 Data Jpa 中进行查询【英文标题】:why do we have to use @Modifying annotation for queries in Data Jpa为什么我们必须使用 @Modifying 注解在 Data Jpa 中进行查询 【发布时间】:2017-09-25 16:19:27 【问题描述】:

例如,我的 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不能分析查询,明白是修改查询?

【问题讨论】:

@Transactional 注解在 Repository 中是不好的做法,最好在你的 Service 中使用它。原因一项业务操作(标记为事务)可能包含对 DB 的多个请求。甚至被几个DAO。更多这里***.com/questions/1079114/… @DanBrandt 除非您使用自定义方法的自定义 repo 实现必须将多个查询作为一个事务执行(eq. 计算所有记录,选择 10 个 id,按 id 选择记录,在 1 中返回所有这些数据对象)。 【参考方案1】:

注意!

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

这样做会触发注释到方法的查询作为更新 查询而不是选择一个。由于 EntityManager 可能包含 执行修改查询后过时的实体,我们做 不会自动清除它(参见 EntityManager.clear() 的 JavaDoc 有关详细信息),因为这有效地删除了所有未刷新的更改 仍待在 EntityManager 中。如果您希望 EntityManager 自动清除,可以设置@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 为什么flushAutomatically

 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 为什么clearAutomatically 在下面考虑 johnUser.active 已经是假的

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

因此,如果 - 在同一事务中 - 您在执行 @Modifying 的行之前或之后使用修改过的对象,则使用 clearAutomaticallyflushAutomatically 如果不是,那么您可以跳过使用这些标志

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

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

【讨论】:

【参考方案2】:

这将触发注释到方法的查询作为更新查询而不是选择一个。由于在执行修改查询后 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 那么你是说在最近的版本中,如果我们只包括修改注解而没有任何像flush或clear这样的属性,那么这个注解是没用的,什么都不做? @theprogrammer 我所说的是默认情况下设置为 false 的那些标志 doc here docs.spring.io/spring-data/jpa/docs/current/api/org/…,关于如果你不设置它们会发生什么?请参阅下面Younas的答案。或官方文档docs.spring.io/spring-data/jpa/docs/current/reference/html/…【参考方案3】:

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

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

【讨论】:

以上是关于为啥我们必须使用 @Modifying 注解在 Data Jpa 中进行查询的主要内容,如果未能解决你的问题,请参考以下文章

SpringData 学习—— 使用 @Modifying 注解完成修改操作

SpringData系列四 @Query注解及@Modifying注解@Query注解及@Modifying注解

Spring Data JPA @Modifying 注解使用@Transactional

SpringData系列四 @Query注解及@Modifying注解

Spring-data-jpa 的@modifying注解

JPA中自定义的插入更新删除方法为什么要添加@Modifying注解和@Transactional注解?