介绍
在我以前的文章中,我描述了应用程序级事务如何为长时间的对话提供合适的并发控制机制。
所有实体都在Hibernate会话的上下文中加载,充当事务后写式缓存 。
Hibernate持久性上下文可以包含给定实体的一个引用和一个引用。 一级缓存可确保会话级可重复读取。
如果对话跨越多个请求,我们可以进行应用程序级的可重复读取。 长时间的对话本质上是有状态的,因此我们可以选择分离的对象或长期的持久性上下文 。 但是,应用程序级可重复读取需要应用程序级并发控制策略,例如乐观锁定。
抓住
但是这种行为有时可能被证明是出乎意料的。
如果您的Hibernate会话已经加载了给定的实体,那么任何后续的实体查询(JPQL / HQL)都将返回完全相同的对象引用(不考虑当前加载的数据库快照):
在此示例中,我们可以看到第一级缓存可防止覆盖已加载的实体。 为了证明这种行为,我提出了以下测试案例:
final ExecutorService executorService = Executors.newSingleThreadExecutor();doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session session) {Product product = new Product();product.setId(1L);product.setQuantity(7L);session.persist(product);return null;}
});doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session session) {final Product product = (Product) session.get(Product.class, 1L);try {executorService.submit(new Callable<Void>() {@Overridepublic Void call() throws Exception {return doInTransaction(new TransactionCallable<Void>() {@Overridepublic Void execute(Session _session) {Product otherThreadProduct = (Product) _session.get(Product.class, 1L);assertNotSame(product, otherThreadProduct);otherThreadProduct.setQuantity(6L);return null;}});}}).get();Product reloadedProduct = (Product) session.createQuery("from Product").uniqueResult();assertEquals(7L, reloadedProduct.getQuantity());assertEquals(6L, ((Number) session.createSQLQuery("select quantity from Product where id = :id").setParameter("id", product.getId()).uniqueResult()).longValue());} catch (Exception e) {fail(e.getMessage());}return null;}
});
该测试案例清楚地说明了实体查询和SQL预测之间的区别。 尽管SQL查询投影总是加载最新的数据库状态,但是实体查询结果由第一级缓存管理,以确保会话级可重复读取。
解决方法1:如果您的用例要求重新加载最新的数据库实体状态,则只需刷新有问题的实体。
解决方法2:如果希望将某个实体与Hibernate一级缓存解除关联,则可以轻松地将其退出 ,因此下一个实体查询可以使用最新的数据库实体值。
超越偏见
休眠是一种手段,而不是目标。 数据访问层既需要读取又需要写入,而普通的JDBC和Hibernate都不是一种千篇一律的解决方案。 数据知识堆栈更适合于获取最多的数据读取查询和写入DML语句。
尽管原生SQL仍然是事实上的关系数据读取技术,但是Hibernate在写入数据方面表现出色。 Hibernate是一个持久性框架,您永远不要忘记这一点。 如果计划将更改传播回数据库,则加载实体是有意义的。 您不需要加载用于显示只读视图的实体,在这种情况下,SQL投影是更好的选择。
会话级可重复读取可防止并发写入场景中的更新丢失,因此,有充分的理由说明实体不会自动刷新。 也许我们选择了手动刷新脏属性 ,并且自动刷新实体可能会覆盖同步的未决更改。
设计数据访问模式并不是一件容易的事,值得投资坚实的集成测试基础。为避免任何未知行为,我强烈建议您验证所有自动生成的SQL语句,以证明其有效性和效率 。
- 代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2014/10/hibernate-application-level-repeatable-reads.html