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

cir*_*rvo 48 spring hibernate spring-data spring-data-jpa spring-boot

我正在使用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();

}
Run Code Online (Sandbox Code Playgroud)

假设我们在DB中有Email [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
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

此方法清除持久性上下文,使其不具有过时的值,但它会删除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;
    }

}
Run Code Online (Sandbox Code Playgroud)

此方法的工作方式与此类似,@Modifying(clearAutomatically = true)但它首先强制EntityManager在执行更新之前刷新对DB的所有更改,然后清除持久性上下文.这样就不会有过时的实体,所有的更改都会保存在DB中.


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


更新2018年

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

@Modifying(flushAutomatically = true, clearAutomatically = true)
Run Code Online (Sandbox Code Playgroud)

Joh*_*mer 8

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

但是我想解释一下您可以使用的JPA方法。因此,您想更改符合特定条件的所有实体,并更新每个实体的值。通常的方法是加载所有需要的实体:

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

然后遍历它们并更新值:

for (Email email : findExpired()) {
  email.setActive(false);
}
Run Code Online (Sandbox Code Playgroud)

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

这个答案是否说“ @Modifying”注释没有用?没有!如果您确保修改后的实体不在本地缓存中(例如,只写应用程序),则此方法只是解决之道。

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

仅用于记录v2:该active列看起来像直接与相关expire。那么,为什么不active完全删除并仅expire在每个查询中查找呢?

  • 重要的是要理解,一旦将实体加载到持久性上下文中,它将被管理,并且除非您调用`refresh()`,否则不会从数据库中重新加载(调用`find()`只会返回已加载的版本)。更新查询只会更新数据库,不会更新任何已管理的实体,而`delete()`查询将更新持久性上下文,因此对`find()`的调用不会返回被查询删除的实体。 (6认同)