Spring Boot Data JPA - 修改更新查询 - 刷新持久性上下文 [英] Spring Boot Data JPA - Modifying update query - Refresh persistence context

查看:71
本文介绍了Spring Boot Data JPA - 修改更新查询 - 刷新持久性上下文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Boot 1.3.0.M4 和 MySQL 数据库.

I'm working with Spring Boot 1.3.0.M4 and a MySQL database.

我在使用修改查询时遇到问题,EntityManager 在查询执行后包含过时的实体.

I have a problem when using modifying queries, the EntityManager contains outdated entities after the query has executed.

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].

Suppose we have Email [id=1, active=true, expire=2015/01/01] in DB.

执行后:

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() 其他实体会丢失一些更改:(

This approach clears the persistence context not to have outdated values, but it drops all non-flushed changes still pending in the EntityManager. As I use only save() methods and not saveAndFlush() some changes are lost for other entities :(

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,然后清除持久性上下文.这样就不会出现过时的实体,所有更改都将保存在数据库中.

This approach works similar to @Modifying(clearAutomatically = true) but it first forces the EntityManager to flush all changes to DB before executing the update and then it clears the persistence context. This way there won't be outdated entities and all changes will be saved in DB.

我想知道是否有更好的方法在 JPA 中执行更新语句,而不会出现过时实体的问题,也无需手动刷新到数据库.也许禁用二级缓存?我如何在 Spring Boot 中做到这一点?

I would like to know if there's a better way to execute update statements in JPA without having the issue of the outdated entities and without the manual flush to DB. Perhaps disabling the 2nd level cache? How can I do it in Spring Boot?

2018 年更新

Spring Data JPA 批准了我的 PR,现在 @Modifying() 中有一个 flushAutomatically 选项.

Spring Data JPA approved my PR, there's a flushAutomatically option in @Modifying() now.

@Modifying(flushAutomatically = true, clearAutomatically = true)

推荐答案

我知道这不是您问题的直接答案,因为您已经在 Github 上构建了一个修复程序并启动了一个拉取请求.谢谢你!

I know this is not a direct answer to your question, since you already have built a fix and started a pull request on Github. Thank you for that!

但我想解释一下您可以采用的 JPA 方式.因此,您希望更改与特定条件匹配的所有实体并更新每个实体的值.正常的方法是加载所有需要的实体:

But I would like to explain the JPA way you can go. So you would like to change all entities which match a specific criteria and update a value on each. The normal approach is just to load all needed entities:

@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();

然后遍历它们并更新值:

Then iterate over them and update the values:

for (Email email : findExpired()) {
  email.setActive(false);
}

现在,hibernate 知道所有更改,如果事务完成或您手动调用 EntityManager.flush(),会将它们写入数据库.我知道如果您有大量数据条目,这将无法正常工作,因为您将所有实体都加载到内存中.但这是保持休眠实体缓存、二级缓存和数据库同步的最佳方式.

Now hibernate knows all changes and will write them to the database if the transaction is done or you call EntityManager.flush() manually. I know this won't work well if you have a big amount of data entries, since you load all entities into memory. But this is the best way, to keep the hibernate entity cache, 2nd level caches and the database in sync.

这个答案是不是说`@Modifying´ 注释没用"?不!如果您确保修改后的实体不在本地缓存中,例如只写应用程序,这种方法就是要走的路.

Does this answer say "the `@Modifying´ annotation is useless"? No! If you ensure the modified entities are not in your local cache e.g. write-only application, this approach is just the way to go.

仅作记录:您的存储库方法不需要 @Transactional.

And just for the record: you don't need @Transactional on your repository methods.

仅用于记录 v2:active 列看起来直接依赖于 expire.那么为什么不完全删除 active 并在每个查询中只查看 expire 呢?

Just for the record v2: the active column looks as it has a direct dependency to expire. So why not delete active completely and look just on expire in every query?

这篇关于Spring Boot Data JPA - 修改更新查询 - 刷新持久性上下文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆