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

时间:2015-08-27 20:29:11

标签: spring hibernate spring-boot spring-data spring-data-jpa

我正在使用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的所有更改,然后清除持久性上下文。这样就不会有过时的实体,所有的更改都会保存在DB中。

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

更新2018年

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

@Modifying(flushAutomatically = true, clearAutomatically = true)

2 个答案:

答案 0 :(得分:5)

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

但我想解释你可以去的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(),它们会将它们写入数据库。我知道如果你有大量的数据条目,这将无法正常工作,因为你将所有实体加载到内存中。但这是保持hibernate实体缓存,二级缓存和数据库同步的最佳方法。

这个答案是否说“@修改'注释是无用的”?没有!如果您确保修改后的实体不在本地缓存中,例如只写应用程序,这种方法才是最佳选择。

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

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

答案 1 :(得分:1)

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