为啥需要在执行修改查询之前清除 jpa 持久性上下文?

Posted

技术标签:

【中文标题】为啥需要在执行修改查询之前清除 jpa 持久性上下文?【英文标题】:Why need to clear jpa persistence context before executing modifying query?为什么需要在执行修改查询之前清除 jpa 持久性上下文? 【发布时间】:2018-09-19 20:56:57 【问题描述】:

我正在使用 Spring Boot 1.5.2.RELEASE 和 PostgreSQL 9.5 数据库。

文件实体:

@Entity
public class File implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String externalUid;

文件存储库如下所示:

public interface FileRepository extends JpaRepository<File, Long> 

    @Modifying
    @Query("UPDATE File f SET f.externalUid =:externalUid WHERE f.id =:id")
    void setExternalLink(@Param("id") Long id, @Param("externalUid") String externalId);

当我保存新文件(使用刷新)并对其执行更新后,然后从存储库中获取此文件,然后我就有了过时的实体。但是当我在执行修改查询之前在entityManager 上执行clear() [1] 时,我得到了更新的实体:

String externalUid = UUID.randomUUID().toString();

File file = new File();
File savedFile = fileRepository.saveAndFlush(file);

// [1] entityManager.clear();

fileRepository.setExternalLink(savedFile.getId(), externalUid);

File updatedFile = fileRepository.findOne(savedFile.getId());

System.out.print(updatedFile.getExternalUid()); // null

这个问题与这个问题非常相似:Spring Boot Data JPA - Modifying update query - Refresh persistence context。但就我而言,我执行saveAndFlush() 而不是save()

在这方面,有人可以解释一下为什么我们需要在执行修改查询之前清除持久性上下文以及为什么存储库会返回过时的实体?

【问题讨论】:

因为实体将从一级缓存中检索,而不是对数据库进行实际查询。清除时,您会从一级缓存中删除实体,并且需要查询才能再次检索它。 【参考方案1】:

主要的一点是休眠缓存对你所做的 DML 样式查询一无所知。

因此,如果您不清除缓存并直接对数据库执行更新,则有问题的缓存对象将过时。

调用clear() 将清空缓存并强制休眠在后续查询中命中数据库。

【讨论】:

实际上这段代码是在春季测试中执行的(带有“回滚”事务),所以在任何情况下(无论是否清除)都没有对数据库进行任何更改... 是的,由于您的 DML 声明而发生变化。由于未检测到缓存(如两次解释),使用 SQL 会绕过对象模型。清除缓存将导致对数据库的命中,对数据库的命中将导致在进行实际检索之前刷新待处理的查询。事务是否已提交的事实与此无关。 嗯..好的。我或多或少都明白了。然后请解释一下为什么在升级到 spring-boot 1.5.11(spring-data-jpa 1.11.11) 并且当我设置@Modifying(flushAutomatically=true) 之后它可以工作,而没有clear()

以上是关于为啥需要在执行修改查询之前清除 jpa 持久性上下文?的主要内容,如果未能解决你的问题,请参考以下文章

执行此操作需要事务(使用事务或扩展持久性上下文)

从 JPA/EJB3 持久性上下文中分离实体

在jpa中将持久性上下文设置为只读

在 JPA 上是不是绕过托管实体

JPA合并与持久化[重复]

通过 JPA EntityManager 执行创建表查询