首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Spring Boot Data JPA -修改更新查询-刷新持久性上下文

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

Stack Overflow用户
提问于 2015-08-28 04:29:11
回答 2查看 29.8K关注 0票数 60

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

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

原始JPA存储库:

代码语言:javascript
复制
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();

}

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

执行后:

代码语言:javascript
复制
emailRepository.save(email);
emailRepository.deactivateByExpired();
System.out.println(emailRepository.findOne(1L).isActive()); // prints true!! it should print false

解决问题的第一种方法: add clearAutomatically = true

代码语言:javascript
复制
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(),因此其他实体的一些更改会丢失:(

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

代码语言:javascript
复制
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在执行更新之前将所有更改刷新到数据库,然后清除持久性上下文。这样就不会有过时的实体,所有的更改都会保存在数据库中。

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

更新2018

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

代码语言:javascript
复制
@Modifying(flushAutomatically = true, clearAutomatically = true)
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-09-18 05:24:55

我知道这不是对你问题的直接回答,因为你已经在Github上构建了一个修复并启动了一个拉取请求。谢谢你这么做!

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

代码语言:javascript
复制
@Query("SELECT * FROM Email e where e.active = true and e.expire <= NOW()")
List<Email> findExpired();

然后迭代它们并更新值:

代码语言:javascript
复制
for (Email email : findExpired()) {
  email.setActive(false);
}

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

这个答案是不是说“`@Modifying‘注解无用”?不是的!如果您确保修改后的实体不在本地缓存中,例如只写应用程序,则此方法就是正确的方法。

需要说明的是:您的存储库方法不需要使用@Transactional

仅针对记录v2:active列看起来与expire有直接的依赖关系。那么为什么不完全删除active,在每个查询中只查看expire呢?

票数 11
EN

Stack Overflow用户

发布于 2017-09-22 15:12:00

正如klaus-groenbaek所说,您可以注入EntityManager并使用它的刷新方法:

代码语言:javascript
复制
@Inject
EntityManager entityManager;

...

emailRepository.save(email);
emailRepository.deactivateByExpired();
Email email2 = emailRepository.findOne(1L);
entityManager.refresh(email2);
System.out.println(email2.isActive()); // prints false
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32258857

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档