Spring Boot Data JPA - 修改更新查询 - 刷新持久化上下文

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32258857/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:45:46  来源:igfitidea点击:

Spring Boot Data JPA - Modifying update query - Refresh persistence context

springhibernatespring-bootspring-dataspring-data-jpa

提问by ciri-cuervo

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

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

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

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

Original JPA Repository:

原始 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();

}

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

假设我们在数据库中有Email [id=1, active=true, expire=2015/01/01]

After executing:

执行后:

emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false


First approach to solve the problem: add clearAutomatically = true

解决问题的第一种方法:添加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();

}

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 :(

这种方法清除了持久性上下文,使其不具有过时的值,但它会丢弃所有在 EntityManager 中仍待处理的非刷新更改。因为我只使用save()方法而不是saveAndFlush()其他实体丢失了一些更改:(



Second approach to solve the problem: custom implementation for repository

解决问题的第二种方法:存储库的自定义实现

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;
    }

}

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.

这种方法的工作原理类似于,@Modifying(clearAutomatically = true)但它首先强制 EntityManager 在执行更新之前将所有更改刷新到 DB,然后清除持久性上下文。这样就不会有过时的实体,所有更改都将保存在数据库中。



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?

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



Update 2018

2018 年更新

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

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

@Modifying(flushAutomatically = true, clearAutomatically = true)

采纳答案by Johannes Leimer

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!

我知道这不是对您问题的直接回答,因为您已经构建了修复程序并在 Github 上发起了拉取请求。谢谢你!

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:

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

@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);
}

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.

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

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.

这个答案是否说“‘@Modifying’注释没用”?不!如果您确保修改后的实体不在您的本地缓存中,例如只写应用程序,那么这种方法就是要走的路。

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

只是为了记录:您不需要@Transactional存储库方法。

Just for the record v2: the activecolumn looks as it has a direct dependency to expire. So why not delete activecompletely and look just on expirein every query?

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

回答by Eric Bonnot

As klaus-groenbaek said, you can inject EntityManager and use its refresh method :

正如 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