在 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 告诉休眠实体是脏的。
当我使用查询 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 告诉休眠实体是脏的。