如何向 Hibernate 标准 api 查询插入“优化器提示”
Posted
技术标签:
【中文标题】如何向 Hibernate 标准 api 查询插入“优化器提示”【英文标题】:How to insert an "Optimizer hint" to Hibernate criteria api query 【发布时间】:2010-11-22 14:04:33 【问题描述】:我有一个使用条件 api 动态组合的休眠查询。 如果按原样执行,它会生成非常慢的查询。
但我注意到,如果我在查询前面加上 /*+ FIRST_ROWS(10) */,它们的速度会快 1000%。我如何使用标准 api 做到这一点?
我尝试了 criteria.setComment(..),但这似乎被忽略了。
在休眠文档 3.4.1.7 中。提到了查询提示,但它明确指出:“请注意,这些不是 SQL 查询提示”
查询的结果会分页,所以在 99% 的情况下我会显示结果 1-10。
【问题讨论】:
【参考方案1】:问题在于提示语法不是注释,它只是看起来有点像。它确实必须在SELECT
和选定的列之间进行,而setComment()
在SELECT
之前添加注释。
除此之外,没有灵丹妙药。 FIRST_ROWS
不是性能增强工具。取回所有行可能最终需要更长的时间。当然,在面向用户的程序中,检索前十行可能就是我们需要做的所有事情。
但是,无论您采用哪种方式,如果您想使用 Oracle 的提示语法,您就需要走原生 SQL 路线。
你还能做什么?我(还)没有太多调整 Hibernate 的经验。有一次我在这样的任务下查询是从一大堆表中获取行来实例化一个具有很多子类型的对象。每个子类型都是一个单独的表。 Hibernate 生成的查询有许多 OUTER JOIN,这使优化器感到困惑。将这个怪物分成几个只使用 INNER JOIN 的集中查询(每个子类型一个),检索时间减少了 200 倍。
这可能对您没有任何直接用途。但原则是,查看 Hibernate 查询,看看它是否可以以不同的、更有效的方式实现。
【讨论】:
结果确实是面向用户的。 查询本身是正确的。由于它是动态生成的,因此无法手动优化,因此每个搜索查询都不同。【参考方案2】:您可以在会话级别修改优化器模式:
ALTER SESSION SET optimizer_mode = FIRST_ROWS;
在您的查询之前,然后将其恢复为默认值 (ALL_ROWS
),或者在您的情况下,因为 99% 的查询会从中受益,您可以在架构级别对其进行修改(使用 ON LOGON
例如触发器)甚至在实例级别(修改 init 参数)。
【讨论】:
【参考方案3】:我可以通过将 ProjectionList 添加到条件中来输入 Oracle 提示。
ProjectionList proList = Projections.projectionList();
proList.add(Projections.sqlProjection("/*+INDEX_DESC(this_ MY_INDEX_NAME)*/ 1 as MYHINT",
new String[],
new Type[]));
//add properties from your class
proList.add(Projections.property("field1"));
proList.add(Projections.property("field2"));
proList.add(Projections.property("field3"));
c.setProjection(proList);
c.list()
按 ProjectionList 的顺序返回 List<Object[]>
【讨论】:
【参考方案4】:我有另一个通用解决方案,它应该适用于每个 Criteria 查询: 使用标准注释和 Hibernate 拦截器将最终 SQL 更改为数据库。 (我在 Hibernate 3.3 中使用它,但应该适用于每个版本,拦截器的注册可能不同。)
在您的查询代码中使用:
criteria.setComment("$HINT$ push_pred(viewAlias)");
编写一个Interceptor 来更改为SQL 文本(这个使用commons.lang3.StringUtils):
public class HibernateEntityInterceptor extends EmptyInterceptor
@Override
public String onPrepareStatement(String sql)
if (sql.startsWith("/* $HINT$"))
String hintText = StringUtils.substringBetween(sql, "/* $HINT$", "*/");
sql = sql.replaceFirst("select ", "select /*+" + hintText + "*/ ");
return sql;
以上是针对 Oracle 的,但应该可以轻松地针对每个 DBMS 进行调整。 也许您可以/应该为提示标记“$HINT$”创建一个常量。 日志记录也应该完成(这样你就可以很容易地看到拦截器的正确调用),为了简单起见,我把它省略了。
拦截器必须注册。在 Spring 中,这是在 applicationContext.xml
中完成的:
<bean id="entityListener" class="your.package.HibernateEntityInterceptor"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="entityInterceptor" ref="entityListener"/>
[...]
或者(从 Hibernate 3.3 文档复制):
在打开会话时指定会话范围的拦截器 使用重载的 SessionFactory.openSession() 方法之一 接受一个拦截器。
Session session = sf.openSession( new HibernateEntityInterceptor() );
一个 SessionFactory 范围的拦截器注册到 构建 SessionFactory 之前的配置对象。除非一个 会话打开显式指定要使用的拦截器, 提供的拦截器将应用于从该打开的所有会话 会话工厂。 SessionFactory 范围的拦截器必须是线程的 安全的。确保您不存储特定于会话的状态,因为 多个会话可能会同时使用此拦截器。
new Configuration().setInterceptor( new HibernateEntityInterceptor() );
【讨论】:
以上是关于如何向 Hibernate 标准 api 查询插入“优化器提示”的主要内容,如果未能解决你的问题,请参考以下文章
在基于方言的 Hibernate 标准 API 中使用不同的投影函数