hibernate自动配置_Hibernate自动冲洗的黑暗面

hibernate自动配置

介绍

既然我已经描述了JPA和Hibernate刷新策略的基础知识 ,我就可以继续阐明Hibernate的AUTO刷新模式的令人惊讶的行为。

并非所有查询都会触发会话刷新

许多人会认为Hibernate 总是在执行任何查询之前先刷新Session。 虽然这可能是一种更直观的方法,并且可能更接近JPA的AUTO FlushModeType ,但是Hibernate尝试对其进行优化。 如果当前执行的查询不会命中未决SQL INSERT / UPDATE / DELETE语句,则不严格要求刷新。

参考文档中所述,AUTO刷新策略有时可能在执行查询之前同步当前持久性上下文。 如果框架作者选择将其命名为FlushMode.SOMETIMES,那将更加直观。

JPQL / HQL和SQL

与许多其他ORM解决方案一样,Hibernate提供了一种非常基于SQL-92语法的有限实体查询语言( JPQL / HQL )。

当前数据库方言将实体查询语言转换为SQL,因此它必须在不同的数据库产品中提供相同的功能。 由于大多数数据库系统都是SQL-92投诉,因此实体查询语言是最常见的数据库查询语法的抽象。

虽然您可以在许多用例(选择实体,甚至是投影)中使用实体查询语言,但有时其有限功能与高级查询请求不匹配。 每当我们想要利用某些特定的查询技术时,例如:

我们别无选择,只能运行本机SQL查询。

Hibernate是一个持久性框架。 Hibernate从未打算取代SQL。 如果在本机查询中更好地表达某些查询,那么就不应该在数据库可移植性的高度上牺牲应用程序的性能。

自动冲洗和HQL / JPQL

首先,我们将测试将要执行HQL查询时AUTO刷新模式的行为。 为此,我们定义了以下不相关的实体:

同花顺

该测试将执行以下操作:

  • 一个人将被坚持。
  • 选择用户不应触发刷新。
  • 查询人员时,AUTO刷新应触发实体状态转换同步(应在执行选择查询之前执行人员INSERT)。
Product product = new Product();
session.persist(product);
assertEquals(0L,  session.createQuery("select count(id) from User").uniqueResult());
assertEquals(product.getId(), session.createQuery("select p.id from Product p").uniqueResult());

提供以下SQL输出:

[main]: o.h.e.i.AbstractSaveEventListener - Generated identifier: f76f61e2-f3e3-4ea4-8f44-82e9804ceed0, using strategy: org.hibernate.id.UUIDGenerator
Query:{[select count(user0_.id) as col_0_0_ from user user0_][]} 
Query:{[insert into product (color, id) values (?, ?)][12,f76f61e2-f3e3-4ea4-8f44-82e9804ceed0]} 
Query:{[select product0_.id as col_0_0_ from product product0_][]}

如您所见,用户选择尚未触发会话刷新。 这是因为Hibernate会根据挂起的表语句检查当前查询空间。 如果当前正在执行的查询与未刷新的表语句不重叠,则可以安全地忽略刷新。

HQL甚至可以检测产品冲洗:

  • 子选择
    session.persist(product);
    assertEquals(0L,  session.createQuery(
        "select count(*) " +
        "from User u " +
        "where u.favoriteColor in (select distinct(p.color) from Product p)").uniqueResult());

    导致正确的冲洗调用:

    Query:{[insert into product (color, id) values (?, ?)][Blue,2d9d1b4f-eaee-45f1-a480-120eb66da9e8]} 
    Query:{[select count(*) as col_0_0_ from user user0_ where user0_.favoriteColor in (select distinct product1_.color from product product1_)][]}
  • 或theta风格的连接
    session.persist(product);
    assertEquals(0L,  session.createQuery(
        "select count(*) " +
        "from User u, Product p " +
        "where u.favoriteColor = p.color").uniqueResult());

    触发预期的冲洗:

    Query:{[insert into product (color, id) values (?, ?)][Blue,4af0b843-da3f-4b38-aa42-1e590db186a9]} 
    Query:{[select count(*) as col_0_0_ from user user0_ cross join product product1_ where user0_.favoriteColor=product1_.color][]}

它起作用的原因是因为已将实体查询解析并转换为SQL查询。 Hibernate无法引用不存在的表,因此它始终知道HQL / JPQL查询将命中的数据库表。

因此,Hibernate仅知道我们在HQL查询中显式引用的那些表。 如果当前待处理的DML语句暗示数据库触发器或数据库级联,则Hibernate将不会意识到这些。 因此,即使对于HQL,AUTO刷新模式也可能导致一致性问题。

自动刷新和本机SQL查询

当涉及本地SQL查询时,事情变得越来越复杂。 Hibernate无法解析SQL查询,因为它仅支持有限的数据库查询语法。 许多数据库系统提供了超越Hibernate Entity Query功能的专有功能。

使用本机SQL查询查询Person表不会触发刷新,从而导致不一致问题:

Product product = new Product();
session.persist(product);
assertNull(session.createSQLQuery("select id from product").uniqueResult());
DEBUG [main]: o.h.e.i.AbstractSaveEventListener - Generated identifier: 718b84d8-9270-48f3-86ff-0b8da7f9af7c, using strategy: org.hibernate.id.UUIDGenerator
Query:{[select id from product][]} 
Query:{[insert into product (color, id) values (?, ?)][12,718b84d8-9270-48f3-86ff-0b8da7f9af7c]}

新保留的产品仅在事务提交期间插入,因为本机SQL查询未触发刷新。 这是主要的一致性问题,许多开发人员很难调试甚至无法预见。 这是始终检查自动生成SQL语句的另一个原因。

即使对于命名的本机查询,也会观察到相同的行为:

@NamedNativeQueries(
    @NamedNativeQuery(name = "product_ids", query = "select id from product")
)
assertNull(session.getNamedQuery("product_ids").uniqueResult());

因此,即使预加载了SQL查询,Hibernate也不会提取关联的查询空间以使其与未决的DML语句匹配。

否决当前的冲洗策略

即使当前会话定义了默认的刷新策略,您也可以始终基于查询覆盖它。

查询刷新模式

ALWAYS模式将在执行任何查询(HQL或SQL)之前刷新持久性上下文。 这次,Hibernate没有应用优化,所有待处理的实体状态转换都将与当前数据库事务同步。

assertEquals(product.getId(), session.createSQLQuery("select id from product").setFlushMode(FlushMode.ALWAYS).uniqueResult());

指示Hibernate应该同步哪些表

您还可以在当前正在执行SQL查询上添加同步规则。 然后,Hibernate将知道在执行查询之前需要同步哪些数据库表。 这对于二级缓存也很有用。

assertEquals(product.getId(), session.createSQLQuery("select id from product").addSynchronizedEntityClass(Product.class).uniqueResult());

结论

自动刷新模式非常棘手,并且在查询基础上解决一致性问题是维护人员的噩梦。 如果决定添加数据库触发器,则必须检查所有Hibernate查询,以确保它们最终不会针对过时的数据运行。

我的建议是使用ALWAYS刷新模式,即使Hibernate作者警告我们:

这种策略几乎总是不必要且效率低下的。

不一致有时是一些过早冲洗的问题。 当混合DML操作和查询可能会导致不必要的刷新时,这种情况很难缓解。 在会话事务期间,最好在事务开始时(当没有待处理的实体状态转换要同步时)和事务结束时(无论如何将刷新当前持久性上下文)执行查询。

实体状态转换操作应在事务结束时进行,以尽量避免将它们与查询操作交错(因此避免过早的刷新触发器)。

翻译自: https://www.javacodegeeks.com/2014/08/the-dark-side-of-hibernate-auto-flush.html

hibernate自动配置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值