在 Hibernate 中同时使用查询 Api 和条件 Api 会导致问题

Using Both Query Api And Criteria Api in Hibernate Leads Problems

当我使用查询 API 更新一行,然后在同一事务中使用条件 API 检索数据时,我得到的是旧值,而不是更新后的值。为什么会这样,我该如何解决这个问题?我需要获取更新后的值。

@Service
@Transactional
public class ExampleServiceImpl implements ExampleService {
    @Autowired
    ExampleRepository exampleRepository;

    @Transactional
    public void example() {
        ExampleEntity entity = (ExampleEntity) sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();

        exampleRepository.updateState(190001L, State.CLOSED);

        ExampleEntity updatedEntity = (ExampleEntity)sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();

        assertEquals(State.CLOSED, updatedEntity.getState());
    }

}

@Repository
public class ExampleRepositoryImpl implements ExampleRepository {
    public void updateState(Long id, State state) {
        String updateScript = "update exampleEntity set state= '%s', " +
                "VERSION = VERSION + 1 " +
                "where ID = %s;";

        updateScript = String.format(updateScript, state, id);

        Query sqlQuery = sessionFactory.getCurrentSession().createSQLQuery(updateScript);

        sqlQuery.executeUpdate();
    }
}

注意:如果我删除了第一行并且没有在开头检索实体,一切都会按我的预期进行。

当您得到结果时,您的交易仍未提交 - 这就是您获得 "old" 值的原因。

您正在混合原生 SQL 和休眠。基本上,当您第一次检索实体时,它会存储在您的会话 EntityManager 中。然后您使用 plain SQL 来更新数据库中的行,但是就休眠而言,实体并没有被弄脏,因为它不够聪明,无法理解 plain SQL 与对象模型。当你第二次检索它时,它只是给你它已经缓存在 EntityManager 中的原始实体,而不是查询数据库。

解决方案是在更新后简单地从 EntityManager 中手动强制逐出实体,如下所示: sessionFactory.getCurrentSession().evict(entity);

或者您可以简单地更新您获取的实体并将其持久化(恕我直言,最佳解决方案,没有多余的 DAO 方法,以及远离数据库的最佳抽象):

ExampleEntity entity = (ExampleEntity) sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();

entity.setState(State.CLOSED);
entity.setVersion(e.getVersion() + 1);

sessionFactory.getCurrentSession().update(entity);

基本上...无论您选择哪个选项,都不要在同一个事务中混合普通 SQL 和休眠查询。一旦 hibernate 加载了一个对象,它就会 return 从它的缓存中取出同一个实体,直到它知道它是脏的。当普通的 SQL 被用来弄脏它时,知道一个实体是脏的还不够聪明。如果你别无选择,必须使用 SQL(在设计良好的休眠模型中永远不会出现这种情况),然后调用 evict 告诉休眠实体是脏的。