Spring 引导数据 JPA - 修改更新查询 - 刷新持久性上下文
Spring Boot Data JPA - Modifying update query - Refresh persistence context
我正在使用 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 中执行更新语句,而不会出现过时实体的问题,也不会手动刷新到 DB。也许禁用二级缓存?我如何在 Spring 引导中执行此操作?
2018 年更新
Spring Data JPA 批准了我的 PR,现在 @Modifying()
中有一个 flushAutomatically
选项。
@Modifying(flushAutomatically = true, clearAutomatically = true)
我知道这不是对您问题的直接回答,因为您已经构建了修复程序并在 Github 上启动了拉取请求。谢谢你!
但我想解释一下您可以采用的 JPA 方式。因此,您想更改所有符合特定条件的实体并更新每个实体的值。正常的方法是加载所有需要的实体:
@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
然后遍历它们并更新值:
for (Email email : findExpired()) {
email.setActive(false);
}
现在 hibernate 知道所有更改,如果事务完成或您手动调用 EntityManager.flush()
,会将它们写入数据库。我知道如果您有大量数据条目,这将不会很好地工作,因为您将所有实体加载到内存中。但这是保持休眠实体缓存、二级缓存和数据库同步的最佳方法。
这个答案是 "the `@Modifying´ annotation is useless" 吗?不!如果您确保修改后的实体不在您的本地缓存中,例如只写应用程序,这种方法只是要走的路。
郑重声明:您的存储库方法不需要 @Transactional
。
仅供记录 v2:active
列看起来直接依赖于 expire
。那么为什么不完全删除 active
并在每个查询中只查看 expire
呢?
正如 klaus-groenbaek 所说,您可以注入 EntityManager 并使用其刷新方法:
@Inject
EntityManager entityManager;
...
emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false
我正在使用 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 中执行更新语句,而不会出现过时实体的问题,也不会手动刷新到 DB。也许禁用二级缓存?我如何在 Spring 引导中执行此操作?
2018 年更新
Spring Data JPA 批准了我的 PR,现在 @Modifying()
中有一个 flushAutomatically
选项。
@Modifying(flushAutomatically = true, clearAutomatically = true)
我知道这不是对您问题的直接回答,因为您已经构建了修复程序并在 Github 上启动了拉取请求。谢谢你!
但我想解释一下您可以采用的 JPA 方式。因此,您想更改所有符合特定条件的实体并更新每个实体的值。正常的方法是加载所有需要的实体:
@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();
然后遍历它们并更新值:
for (Email email : findExpired()) {
email.setActive(false);
}
现在 hibernate 知道所有更改,如果事务完成或您手动调用 EntityManager.flush()
,会将它们写入数据库。我知道如果您有大量数据条目,这将不会很好地工作,因为您将所有实体加载到内存中。但这是保持休眠实体缓存、二级缓存和数据库同步的最佳方法。
这个答案是 "the `@Modifying´ annotation is useless" 吗?不!如果您确保修改后的实体不在您的本地缓存中,例如只写应用程序,这种方法只是要走的路。
郑重声明:您的存储库方法不需要 @Transactional
。
仅供记录 v2:active
列看起来直接依赖于 expire
。那么为什么不完全删除 active
并在每个查询中只查看 expire
呢?
正如 klaus-groenbaek 所说,您可以注入 EntityManager 并使用其刷新方法:
@Inject
EntityManager entityManager;
...
emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false