【发布时间】:2015-11-22 09:29:39
【问题描述】:
我正在使用 Spring Boot 1.3.0.M4 和 MySQL 数据库。
我在使用修改查询时遇到问题,EntityManager 在查询执行后包含过时的实体。
原始 JPA 存储库:
public interface EmailRepository extends JpaRepository<Email, Long> {
@Transactional
@Modifying
@Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
假设我们在数据库中有电子邮件 [id=1, active=true, expire=2015/01/01]。
执行后:
emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false
解决问题的第一种方法:添加clearAutomatically = true
public interface EmailRepository extends JpaRepository<Email, Long> {
@Transactional
@Modifying(clearAutomatically = true)
@Query("update Email e set e.active = false where e.active = true and e.expire <= NOW()")
Integer deactivateByExpired();
}
此方法清除持久性上下文以不包含过时的值,但它会丢弃所有仍在 EntityManager 中挂起的未刷新更改。因为我只使用 save() 方法而不是 saveAndFlush() 其他实体丢失了一些更改:(
解决问题的第二种方法:存储库的自定义实现
public interface EmailRepository extends JpaRepository<Email, Long>, EmailRepositoryCustom {
}
public interface EmailRepositoryCustom {
Integer deactivateByExpired();
}
public class EmailRepositoryImpl implements EmailRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Transactional
@Override
public Integer deactivateByExpired() {
String hsql = "update Email e set e.active = false where e.active = true and e.expire <= NOW()";
Query query = entityManager.createQuery(hsql);
entityManager.flush();
Integer result = query.executeUpdate();
entityManager.clear();
return result;
}
}
这种方法的工作方式类似于@Modifying(clearAutomatically = true),但它首先强制 EntityManager 在执行更新之前刷新对 DB 的所有更改,然后清除持久性上下文。这样就不会出现过时的实体,所有更改都将保存在数据库中。
我想知道是否有更好的方法在 JPA 中执行更新语句,而不会出现过时实体的问题,也无需手动刷新到数据库。也许禁用二级缓存?我如何在 Spring Boot 中做到这一点?
2018 年更新
Spring Data JPA 批准了我的 PR,现在 @Modifying() 中有一个 flushAutomatically 选项。
@Modifying(flushAutomatically = true, clearAutomatically = true)
【问题讨论】:
-
二级缓存在这里无关紧要。实体保存在一级缓存中。之前冲洗是适当的解决方案。您可以在 Spring-data-JPA 错误存储库中将此主题作为 RFE 提出。由于您可以通过注释在查询后自动清除,因此我发现能够通过附加的
flushAutomatically属性在查询之前自动刷新也是正常的。也就是说,您也可以简单地保留您的第一个解决方案,并在执行查询之前显式地刷新。 -
我在 Spring Data JIRA DATAJPA-806: add flushAutomatically attribute to @Modifying annotation987654321@创建了一张票
-
在 Spring Data JPA 存储库上创建了拉取请求:github.com/spring-projects/spring-data-jpa/pull/172
-
flushAutomatically 来了
-
@Modifying(flushAutomatically = true, clearAutomatically = true) 拯救了我的一天。我准备明年重复我的项目,这个答案救了我。
标签: spring hibernate spring-boot spring-data spring-data-jpa