如何在Hibernate条件api查询中插入“优化器提示”

时间:2009-08-25 10:56:59

标签: java sql oracle hibernate criteria

我有一个使用条件api动态组合的hibernate查询。 如果按原样执行,它会生成难以忍受的慢速查询。

但是我注意到如果我在查询中添加/ * + FIRST_ROWS(10)* /,它们的速度提高了大约1000%。我怎么能用标准api做到这一点?

我尝试过criteria.setComment(..),但这似乎被忽略了。

在hibernate docs中,3.4.1.7。提到了查询提示,但它明确指出:“请注意,这些不是SQL查询提示”

查询的结果将被分页,因此在99%的情况下,我将显示结果1-10。

4 个答案:

答案 0 :(得分:6)

我能够通过在标准中添加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[]>

答案 1 :(得分:5)

您可以在会话级别修改优化程序模式:

ALTER SESSION SET optimizer_mode = FIRST_ROWS;

在您的查询之前,然后将其恢复为默认值(ALL_ROWS),或者在您的情况下,因为99%的查询将从中受益,您可以在架构级别修改它(使用{ {1}}触发器例程)或甚至在实例级别(修改init参数)。

答案 2 :(得分:5)

我有另一个通用解决方案,它适用于每个Criteria查询:
使用标准注释和Hibernate Interceptor将最终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 也许你可以/应该为提示标记创建一个常量&#34; $ HINT $&#34;。
也应该进行记录(这样你就可以很容易地看到Interceptor的正确调用),为了简单起见,我把它留在了上面。

拦截器必须注册。在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之前的配置对象。除非a   会话打开显式指定要使用的拦截器,   提供的拦截器将应用于从中打开的所有会话   SessionFactory的。 SessionFactory范围的拦截器必须是线程   安全。确保您不存储特定于会话的状态,因为   多个会话可能同时使用此拦截器。

     

new Configuration().setInterceptor( new HibernateEntityInterceptor() );

答案 3 :(得分:1)

问题是提示语法不是注释,它看起来有点像。它必须介于SELECT和所选列之间,而setComment()会在SELECT之前添加评论。

除此之外,没有银子弹。 FIRST_ROWS不是性能增强工具。最终可能需要更长时间才能将所有行返回。当然,在面向用户的程序中,检索前十行可能是我们需要做的全部。

但是,如果你想使用Oracle的提示语法,你需要沿着Native SQL路线走下去。

你还能做什么?我(还)没有很多调整Hibernate的经验。我有一次在这样的任务下,查询是从一大堆表中获取行来实例化一个包含许多子类型的对象。每个子类型都是一个单独的表。 Hibernate生成的查询有很多OUTER JOIN,这使得优化器混乱不堪。将该怪物分解为几个仅使用INNER JOIN的聚焦查询(每个子类型一个),使检索时间减少了200倍。

这可能对你没有任何直接的用处。但原则是,查看Hibernate查询,看看它是否可以以不同的,更有效的方式实现。