我正在使用Spring Boot 1.3.0.M4和MySQL数据库。
在使用修改查询时,我遇到了一个问题,即查询执行后,EntityManager包含过时的实体。
原始JPA Repository:
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]。
执行以下操作后:
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();
}
这种方法清除持久性上下文,以避免过时的值,但它仍会放弃实体管理器中所有未刷新的更改。由于我只使用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将所有更改刷新到数据库,然后清除持久性上下文。这样就不会有过时的实体,所有更改都将保存在数据库中。
我想知道是否有更好的方法在JPA中执行更新语句,而不会出现过时的实体,并且不需要手动刷新到数据库。也许禁用第二级缓存?我该如何在Spring Boot中实现它?
更新2018年
Spring Data JPA通过了我的PR,现在在@Modifying()
中有一个flushAutomatically
选项。
@Modifying(flushAutomatically = true, clearAutomatically = true)
flushAutomatically
属性来实现。话虽如此,您也可以保持第一个解决方案,并在执行查询之前显式地进行刷新。 - JB Nizet