2011-03-29 25 views
10

我有這個測試HQL:爲什麼hibernate hql不同導致左連接上的一個sql獨特?

select distinct o from Order o left join fetch o.lineItems 

,它生成不同的SQL沒有明顯的理由:

select distinct order0_.id as id61_0_, orderline1_.order_id as order1_62_1_... 

的SQL結果集始終是相同的(使用和不使用SQL不同):

order id | order name | orderline id | orderline name 
---------+------------+--------------+--------------- 
     1 | foo  |   1 | foo item 
     1 | foo  |   2 | bar item 
     1 | foo  |   3 | test item 
     2 | empty  |   NULL | NULL 
     3 | bar  |   4 | qwerty item 
     3 | bar  |   5 | asdfgh item 

爲什麼hibernate生成SQL不同? SQL區別沒有任何意義,並使查詢比需要的慢。 這與其中提到,在這種情況下,不同的HQL是公正的結果變壓器快捷方式的FAQ

session.createQuery(「O 從訂單選擇不同的Ø左連接抓取 o.lineItems」 ).LIST();

看起來你在這裏使用SQL DISTINCT關鍵字。當然,這不是SQL,這是HQL。在這種情況下,這個區別只是結果變換器的一個捷徑。是的,在其他情況下,HQL不同將直接轉換爲SQL DISTINCT。不是在這種情況下:您不能在SQL級別過濾出重複項,產品/聯接的本質禁止這種情況 - 您需要重複項或者不需要獲取所有需要的數據。

感謝

+0

lineitems如何映射爲List或Set?首先允許重複和不同意義,後者不是。 – Osw 2011-03-29 11:43:48

+0

設置,如常見問題。 – cherouvim 2011-03-29 12:07:52

+0

我也遇到過這個問題。 Postgres似乎並沒有優化這個冗餘的獨特性,所以這個獨特性肯定會影響Postgres數據庫的執行計劃。我相信這是Hibernate的錯誤。 – vbezhenar 2014-12-22 11:59:59

回答

3

有一個在SQL語句休眠產生細看 - 是的,它確實使用了「獨特」的關鍵字,但不是在方式,我認爲你是它期待(或方式的Hibernate FAQ意味着)即返回一組「獨特」或「唯一」的訂單。

它不使用distinct關鍵字來返回不同的命令,因爲在SQL查詢中這是沒有意義的,考慮到您也指定的連接。

生成的sql集仍然需要由ResultTransformer進行處理,因爲sql集包含重複的訂單。這就是爲什麼他們說HQL distinct關鍵字不直接映射到SQL distinct關鍵字。

+0

它確實生成了一個不同的SQL(和一個ResultTransformer一起)。 SQL區別沒有任何意義,並使查詢比需要的慢。 – cherouvim 2011-03-31 06:44:51

+1

那可能是一個單獨的問題。如果您認爲每個生成的SQL查詢都應該是最優的,那麼您可能會期望太多,這不是Hibernate的目的。 – 2011-04-01 00:38:38

+1

請再次閱讀該問題。這是我唯一確切的問題。它對比了文檔所說的內容。謝謝。 – cherouvim 2011-04-01 07:02:02

1

我有完全相同的問題,我認爲這是一個Hibernate問題(不是錯誤,因爲代碼不會失敗)。但是,我必須深入挖掘以確保它是一個問題。 Hibernate(至少在版本4中,我正在處理我的項目,尤其是4.3.11版本)使用SPI的概念,長話短說:它像一個API來擴展或修改框架。

我把這個功能的優勢來替代類org.hibernate.hql.internal.ast.ASTQueryTranslatorFactoryorg.hibernate.hql.internal.ast.QueryTranslatorImpl(此類由Hibernate和代表呼籲生成的SQL查詢的工作)(這是排序的內部類是由org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory調用,生成實際的SQL查詢)。我做到了,如下所示:

置換org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory

package org.hibernate.hql.internal.ast; 

import java.util.Map; 
import org.hibernate.engine.query.spi.EntityGraphQueryHint; 
import org.hibernate.engine.spi.SessionFactoryImplementor; 
import org.hibernate.hql.spi.QueryTranslator; 

public class NoDistinctInSQLASTQueryTranslatorFactory extends ASTQueryTranslatorFactory { 

    @Override 
    public QueryTranslator createQueryTranslator(String queryIdentifier, String queryString, Map filters, SessionFactoryImplementor factory, EntityGraphQueryHint entityGraphQueryHint) { 
     return new NoDistinctInSQLQueryTranslatorImpl(queryIdentifier, queryString, filters, factory, entityGraphQueryHint); 
    } 

} 

置換org.hibernate.hql.internal.ast.QueryTranslatorImpl

package org.hibernate.hql.internal.ast; 

import java.io.Serializable; 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Map; 
import java.util.Set; 

import org.hibernate.HibernateException; 
import org.hibernate.MappingException; 
import org.hibernate.QueryException; 
import org.hibernate.ScrollableResults; 
import org.hibernate.engine.query.spi.EntityGraphQueryHint; 
import org.hibernate.engine.spi.QueryParameters; 
import org.hibernate.engine.spi.RowSelection; 
import org.hibernate.engine.spi.SessionFactoryImplementor; 
import org.hibernate.engine.spi.SessionImplementor; 
import org.hibernate.event.spi.EventSource; 
import org.hibernate.hql.internal.QueryExecutionRequestException; 
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; 
import org.hibernate.hql.internal.antlr.HqlTokenTypes; 
import org.hibernate.hql.internal.antlr.SqlTokenTypes; 
import org.hibernate.hql.internal.ast.exec.BasicExecutor; 
import org.hibernate.hql.internal.ast.exec.DeleteExecutor; 
import org.hibernate.hql.internal.ast.exec.MultiTableDeleteExecutor; 
import org.hibernate.hql.internal.ast.exec.MultiTableUpdateExecutor; 
import org.hibernate.hql.internal.ast.exec.StatementExecutor; 
import org.hibernate.hql.internal.ast.tree.AggregatedSelectExpression; 
import org.hibernate.hql.internal.ast.tree.FromElement; 
import org.hibernate.hql.internal.ast.tree.InsertStatement; 
import org.hibernate.hql.internal.ast.tree.QueryNode; 
import org.hibernate.hql.internal.ast.tree.Statement; 
import org.hibernate.hql.internal.ast.util.ASTPrinter; 
import org.hibernate.hql.internal.ast.util.ASTUtil; 
import org.hibernate.hql.internal.ast.util.NodeTraverser; 
import org.hibernate.hql.spi.FilterTranslator; 
import org.hibernate.hql.spi.ParameterTranslations; 
import org.hibernate.internal.CoreMessageLogger; 
import org.hibernate.internal.util.ReflectHelper; 
import org.hibernate.internal.util.StringHelper; 
import org.hibernate.internal.util.collections.IdentitySet; 
import org.hibernate.loader.hql.QueryLoader; 
import org.hibernate.param.ParameterSpecification; 
import org.hibernate.persister.entity.Queryable; 
import org.hibernate.type.Type; 

import org.jboss.logging.Logger; 

import antlr.ANTLRException; 
import antlr.RecognitionException; 
import antlr.TokenStreamException; 
import antlr.collections.AST; 

/** 
* A QueryTranslator that uses an Antlr-based parser. 
* 
* @author Joshua Davis ([email protected]) 
*/ 
public class NoDistinctInSQLQueryTranslatorImpl extends QueryTranslatorImpl implements FilterTranslator { 

    private static final CoreMessageLogger LOG = Logger.getMessageLogger(
      CoreMessageLogger.class, 
      QueryTranslatorImpl.class.getName() 
    ); 

    private SessionFactoryImplementor factory; 

    private final String queryIdentifier; 
    private String hql; 
    private boolean shallowQuery; 
    private Map tokenReplacements; 

    //TODO:this is only needed during compilation .. can we eliminate the instvar? 
    private Map enabledFilters; 

    private boolean compiled; 
    private QueryLoader queryLoader; 
    private StatementExecutor statementExecutor; 

    private Statement sqlAst; 
    private String sql; 

    private ParameterTranslations paramTranslations; 
    private List<ParameterSpecification> collectedParameterSpecifications; 

    private EntityGraphQueryHint entityGraphQueryHint; 

    /** 
    * Creates a new AST-based query translator. 
    * 
    * @param queryIdentifier The query-identifier (used in stats collection) 
    * @param query The hql query to translate 
    * @param enabledFilters Currently enabled filters 
    * @param factory The session factory constructing this translator instance. 
    */ 
    public NoDistinctInSQLQueryTranslatorImpl(
      String queryIdentifier, 
      String query, 
      Map enabledFilters, 
      SessionFactoryImplementor factory) { 
     super(queryIdentifier, query, enabledFilters, factory); 
     this.queryIdentifier = queryIdentifier; 
     this.hql = query; 
     this.compiled = false; 
     this.shallowQuery = false; 
     this.enabledFilters = enabledFilters; 
     this.factory = factory; 
    } 

    public NoDistinctInSQLQueryTranslatorImpl(
      String queryIdentifier, 
      String query, 
      Map enabledFilters, 
      SessionFactoryImplementor factory, 
      EntityGraphQueryHint entityGraphQueryHint) { 
     this(queryIdentifier, query, enabledFilters, factory); 
     this.entityGraphQueryHint = entityGraphQueryHint; 
    } 

    /** 
    * Compile a "normal" query. This method may be called multiple times. 
    * Subsequent invocations are no-ops. 
    * 
    * @param replacements Defined query substitutions. 
    * @param shallow Does this represent a shallow (scalar or entity-id) 
    * select? 
    * @throws QueryException There was a problem parsing the query string. 
    * @throws MappingException There was a problem querying defined mappings. 
    */ 
    @Override 
    public void compile(
      Map replacements, 
      boolean shallow) throws QueryException, MappingException { 
     doCompile(replacements, shallow, null); 
    } 

    /** 
    * Compile a filter. This method may be called multiple times. Subsequent 
    * invocations are no-ops. 
    * 
    * @param collectionRole the role name of the collection used as the basis 
    * for the filter. 
    * @param replacements Defined query substitutions. 
    * @param shallow Does this represent a shallow (scalar or entity-id) 
    * select? 
    * @throws QueryException There was a problem parsing the query string. 
    * @throws MappingException There was a problem querying defined mappings. 
    */ 
    @Override 
    public void compile(
      String collectionRole, 
      Map replacements, 
      boolean shallow) throws QueryException, MappingException { 
     doCompile(replacements, shallow, collectionRole); 
    } 

    /** 
    * Performs both filter and non-filter compiling. 
    * 
    * @param replacements Defined query substitutions. 
    * @param shallow Does this represent a shallow (scalar or entity-id) 
    * select? 
    * @param collectionRole the role name of the collection used as the basis 
    * for the filter, NULL if this is not a filter. 
    */ 
    private synchronized void doCompile(Map replacements, boolean shallow, String collectionRole) { 
     // If the query is already compiled, skip the compilation. 
     if (compiled) { 
      LOG.debug("compile() : The query is already compiled, skipping..."); 
      return; 
     } 

     // Remember the parameters for the compilation. 
     this.tokenReplacements = replacements; 
     if (tokenReplacements == null) { 
      tokenReplacements = new HashMap(); 
     } 
     this.shallowQuery = shallow; 

     try { 
      // PHASE 1 : Parse the HQL into an AST. 
      final HqlParser parser = parse(true); 

      // PHASE 2 : Analyze the HQL AST, and produce an SQL AST. 
      final HqlSqlWalker w = analyze(parser, collectionRole); 

      sqlAst = (Statement) w.getAST(); 

      // at some point the generate phase needs to be moved out of here, 
      // because a single object-level DML might spawn multiple SQL DML 
      // command executions. 
      // 
      // Possible to just move the sql generation for dml stuff, but for 
      // consistency-sake probably best to just move responsiblity for 
      // the generation phase completely into the delegates 
      // (QueryLoader/StatementExecutor) themselves. Also, not sure why 
      // QueryLoader currently even has a dependency on this at all; does 
      // it need it? Ideally like to see the walker itself given to the delegates directly... 
      if (sqlAst.needsExecutor()) { 
       statementExecutor = buildAppropriateStatementExecutor(w); 
      } else { 
       // PHASE 3 : Generate the SQL. 
       generate((QueryNode) sqlAst); 
       queryLoader = new QueryLoader(this, factory, w.getSelectClause()); 
      } 

      compiled = true; 
     } catch (QueryException qe) { 
      if (qe.getQueryString() == null) { 
       throw qe.wrapWithQueryString(hql); 
      } else { 
       throw qe; 
      } 
     } catch (RecognitionException e) { 
      // we do not actually propagate ANTLRExceptions as a cause, so 
      // log it here for diagnostic purposes 
      LOG.trace("Converted antlr.RecognitionException", e); 
      throw QuerySyntaxException.convert(e, hql); 
     } catch (ANTLRException e) { 
      // we do not actually propagate ANTLRExceptions as a cause, so 
      // log it here for diagnostic purposes 
      LOG.trace("Converted antlr.ANTLRException", e); 
      throw new QueryException(e.getMessage(), hql); 
     } 

     //only needed during compilation phase... 
     this.enabledFilters = null; 
    } 

    private void generate(AST sqlAst) throws QueryException, RecognitionException { 
     if (sql == null) { 
      final SqlGenerator gen = new SqlGenerator(factory); 
      gen.statement(sqlAst); 
      sql = gen.getSQL(); 
      //Hack: The distinct operator is removed from the sql 
      //string to avoid executing a distinct query in the db server when 
      //the distinct is used in hql. 
      sql = sql.replace("distinct", ""); 
      if (LOG.isDebugEnabled()) { 
       LOG.debugf("HQL: %s", hql); 
       LOG.debugf("SQL: %s", sql); 
      } 
      gen.getParseErrorHandler().throwQueryException(); 
      collectedParameterSpecifications = gen.getCollectedParameters(); 
     } 
    } 

    private static final ASTPrinter SQL_TOKEN_PRINTER = new ASTPrinter(SqlTokenTypes.class); 

    private HqlSqlWalker analyze(HqlParser parser, String collectionRole) throws QueryException, RecognitionException { 
     final HqlSqlWalker w = new HqlSqlWalker(this, factory, parser, tokenReplacements, collectionRole); 
     final AST hqlAst = parser.getAST(); 

     // Transform the tree. 
     w.statement(hqlAst); 

     if (LOG.isDebugEnabled()) { 
      LOG.debug(SQL_TOKEN_PRINTER.showAsString(w.getAST(), "--- SQL AST ---")); 
     } 

     w.getParseErrorHandler().throwQueryException(); 

     return w; 
    } 

    private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException { 
     // Parse the query string into an HQL AST. 
     final HqlParser parser = HqlParser.getInstance(hql); 
     parser.setFilter(filter); 

     LOG.debugf("parse() - HQL: %s", hql); 
     parser.statement(); 

     final AST hqlAst = parser.getAST(); 

     final NodeTraverser walker = new NodeTraverser(new JavaConstantConverter()); 
     walker.traverseDepthFirst(hqlAst); 

     showHqlAst(hqlAst); 

     parser.getParseErrorHandler().throwQueryException(); 
     return parser; 
    } 

    private static final ASTPrinter HQL_TOKEN_PRINTER = new ASTPrinter(HqlTokenTypes.class); 

    @Override 
    void showHqlAst(AST hqlAst) { 
     if (LOG.isDebugEnabled()) { 
      LOG.debug(HQL_TOKEN_PRINTER.showAsString(hqlAst, "--- HQL AST ---")); 
     } 
    } 

    private void errorIfDML() throws HibernateException { 
     if (sqlAst.needsExecutor()) { 
      throw new QueryExecutionRequestException("Not supported for DML operations", hql); 
     } 
    } 

    private void errorIfSelect() throws HibernateException { 
     if (!sqlAst.needsExecutor()) { 
      throw new QueryExecutionRequestException("Not supported for select queries", hql); 
     } 
    } 

    @Override 
    public String getQueryIdentifier() { 
     return queryIdentifier; 
    } 

    @Override 
    public Statement getSqlAST() { 
     return sqlAst; 
    } 

    private HqlSqlWalker getWalker() { 
     return sqlAst.getWalker(); 
    } 

    /** 
    * Types of the return values of an <tt>iterate()</tt> style query. 
    * 
    * @return an array of <tt>Type</tt>s. 
    */ 
    @Override 
    public Type[] getReturnTypes() { 
     errorIfDML(); 
     return getWalker().getReturnTypes(); 
    } 

    @Override 
    public String[] getReturnAliases() { 
     errorIfDML(); 
     return getWalker().getReturnAliases(); 
    } 

    @Override 
    public String[][] getColumnNames() { 
     errorIfDML(); 
     return getWalker().getSelectClause().getColumnNames(); 
    } 

    @Override 
    public Set<Serializable> getQuerySpaces() { 
     return getWalker().getQuerySpaces(); 
    } 

    @Override 
    public List list(SessionImplementor session, QueryParameters queryParameters) 
      throws HibernateException { 
     // Delegate to the QueryLoader... 
     errorIfDML(); 

     final QueryNode query = (QueryNode) sqlAst; 
     final boolean hasLimit = queryParameters.getRowSelection() != null && queryParameters.getRowSelection().definesLimits(); 
     final boolean needsDistincting = (query.getSelectClause().isDistinct() || hasLimit) && containsCollectionFetches(); 

     QueryParameters queryParametersToUse; 
     if (hasLimit && containsCollectionFetches()) { 
      LOG.firstOrMaxResultsSpecifiedWithCollectionFetch(); 
      RowSelection selection = new RowSelection(); 
      selection.setFetchSize(queryParameters.getRowSelection().getFetchSize()); 
      selection.setTimeout(queryParameters.getRowSelection().getTimeout()); 
      queryParametersToUse = queryParameters.createCopyUsing(selection); 
     } else { 
      queryParametersToUse = queryParameters; 
     } 

     List results = queryLoader.list(session, queryParametersToUse); 

     if (needsDistincting) { 
      int includedCount = -1; 
      // NOTE : firstRow is zero-based 
      int first = !hasLimit || queryParameters.getRowSelection().getFirstRow() == null 
        ? 0 
        : queryParameters.getRowSelection().getFirstRow(); 
      int max = !hasLimit || queryParameters.getRowSelection().getMaxRows() == null 
        ? -1 
        : queryParameters.getRowSelection().getMaxRows(); 
      List tmp = new ArrayList(); 
      IdentitySet distinction = new IdentitySet(); 
      for (final Object result : results) { 
       if (!distinction.add(result)) { 
        continue; 
       } 
       includedCount++; 
       if (includedCount < first) { 
        continue; 
       } 
       tmp.add(result); 
       // NOTE : (max - 1) because first is zero-based while max is not... 
       if (max >= 0 && (includedCount - first) >= (max - 1)) { 
        break; 
       } 
      } 
      results = tmp; 
     } 

     return results; 
    } 

    /** 
    * Return the query results as an iterator 
    */ 
    @Override 
    public Iterator iterate(QueryParameters queryParameters, EventSource session) 
      throws HibernateException { 
     // Delegate to the QueryLoader... 
     errorIfDML(); 
     return queryLoader.iterate(queryParameters, session); 
    } 

    /** 
    * Return the query results, as an instance of <tt>ScrollableResults</tt> 
    */ 
    @Override 
    public ScrollableResults scroll(QueryParameters queryParameters, SessionImplementor session) 
      throws HibernateException { 
     // Delegate to the QueryLoader... 
     errorIfDML(); 
     return queryLoader.scroll(queryParameters, session); 
    } 

    @Override 
    public int executeUpdate(QueryParameters queryParameters, SessionImplementor session) 
      throws HibernateException { 
     errorIfSelect(); 
     return statementExecutor.execute(queryParameters, session); 
    } 

    /** 
    * The SQL query string to be called; implemented by all subclasses 
    */ 
    @Override 
    public String getSQLString() { 
     return sql; 
    } 

    @Override 
    public List<String> collectSqlStrings() { 
     ArrayList<String> list = new ArrayList<>(); 
     if (isManipulationStatement()) { 
      String[] sqlStatements = statementExecutor.getSqlStatements(); 
      Collections.addAll(list, sqlStatements); 
     } else { 
      list.add(sql); 
     } 
     return list; 
    } 

    // -- Package local methods for the QueryLoader delegate -- 
    @Override 
    public boolean isShallowQuery() { 
     return shallowQuery; 
    } 

    @Override 
    public String getQueryString() { 
     return hql; 
    } 

    @Override 
    public Map getEnabledFilters() { 
     return enabledFilters; 
    } 

    @Override 
    public int[] getNamedParameterLocs(String name) { 
     return getWalker().getNamedParameterLocations(name); 
    } 

    @Override 
    public boolean containsCollectionFetches() { 
     errorIfDML(); 
     List collectionFetches = ((QueryNode) sqlAst).getFromClause().getCollectionFetches(); 
     return collectionFetches != null && collectionFetches.size() > 0; 
    } 

    @Override 
    public boolean isManipulationStatement() { 
     return sqlAst.needsExecutor(); 
    } 

    @Override 
    public void validateScrollability() throws HibernateException { 
     // Impl Note: allows multiple collection fetches as long as the 
     // entire fecthed graph still "points back" to a single 
     // root entity for return 

     errorIfDML(); 

     final QueryNode query = (QueryNode) sqlAst; 

     // If there are no collection fetches, then no further checks are needed 
     List collectionFetches = query.getFromClause().getCollectionFetches(); 
     if (collectionFetches.isEmpty()) { 
      return; 
     } 

     // A shallow query is ok (although technically there should be no fetching here...) 
     if (isShallowQuery()) { 
      return; 
     } 

     // Otherwise, we have a non-scalar select with defined collection fetch(es). 
     // Make sure that there is only a single root entity in the return (no tuples) 
     if (getReturnTypes().length > 1) { 
      throw new HibernateException("cannot scroll with collection fetches and returned tuples"); 
     } 

     FromElement owner = null; 
     for (Object o : query.getSelectClause().getFromElementsForLoad()) { 
      // should be the first, but just to be safe... 
      final FromElement fromElement = (FromElement) o; 
      if (fromElement.getOrigin() == null) { 
       owner = fromElement; 
       break; 
      } 
     } 

     if (owner == null) { 
      throw new HibernateException("unable to locate collection fetch(es) owner for scrollability checks"); 
     } 

     // This is not strictly true. We actually just need to make sure that 
     // it is ordered by root-entity PK and that that order-by comes before 
     // any non-root-entity ordering... 
     AST primaryOrdering = query.getOrderByClause().getFirstChild(); 
     if (primaryOrdering != null) { 
      // TODO : this is a bit dodgy, come up with a better way to check this (plus see above comment) 
      String[] idColNames = owner.getQueryable().getIdentifierColumnNames(); 
      String expectedPrimaryOrderSeq = StringHelper.join(
        ", ", 
        StringHelper.qualify(owner.getTableAlias(), idColNames) 
      ); 
      if (!primaryOrdering.getText().startsWith(expectedPrimaryOrderSeq)) { 
       throw new HibernateException("cannot scroll results with collection fetches which are not ordered primarily by the root entity's PK"); 
      } 
     } 
    } 

    private StatementExecutor buildAppropriateStatementExecutor(HqlSqlWalker walker) { 
     final Statement statement = (Statement) walker.getAST(); 
     switch (walker.getStatementType()) { 
      case HqlSqlTokenTypes.DELETE: { 
       final FromElement fromElement = walker.getFinalFromClause().getFromElement(); 
       final Queryable persister = fromElement.getQueryable(); 
       if (persister.isMultiTable()) { 
        return new MultiTableDeleteExecutor(walker); 
       } else { 
        return new DeleteExecutor(walker, persister); 
       } 
      } 
      case HqlSqlTokenTypes.UPDATE: { 
       final FromElement fromElement = walker.getFinalFromClause().getFromElement(); 
       final Queryable persister = fromElement.getQueryable(); 
       if (persister.isMultiTable()) { 
        // even here, if only properties mapped to the "base table" are referenced 
        // in the set and where clauses, this could be handled by the BasicDelegate. 
        // TODO : decide if it is better performance-wise to doAfterTransactionCompletion that check, or to simply use the MultiTableUpdateDelegate 
        return new MultiTableUpdateExecutor(walker); 
       } else { 
        return new BasicExecutor(walker, persister); 
       } 
      } 
      case HqlSqlTokenTypes.INSERT: 
       return new BasicExecutor(walker, ((InsertStatement) statement).getIntoClause().getQueryable()); 
      default: 
       throw new QueryException("Unexpected statement type"); 
     } 
    } 

    @Override 
    public ParameterTranslations getParameterTranslations() { 
     if (paramTranslations == null) { 
      paramTranslations = new ParameterTranslationsImpl(getWalker().getParameters()); 
     } 
     return paramTranslations; 
    } 

    @Override 
    public List<ParameterSpecification> getCollectedParameterSpecifications() { 
     return collectedParameterSpecifications; 
    } 

    @Override 
    public Class getDynamicInstantiationResultType() { 
     AggregatedSelectExpression aggregation = queryLoader.getAggregatedSelectExpression(); 
     return aggregation == null ? null : aggregation.getAggregationResultType(); 
    } 

    public static class JavaConstantConverter implements NodeTraverser.VisitationStrategy { 

     private AST dotRoot; 

     @Override 
     public void visit(AST node) { 
      if (dotRoot != null) { 
       // we are already processing a dot-structure 
       if (ASTUtil.isSubtreeChild(dotRoot, node)) { 
        return; 
       } 
       // we are now at a new tree level 
       dotRoot = null; 
      } 

      if (node.getType() == HqlTokenTypes.DOT) { 
       dotRoot = node; 
       handleDotStructure(dotRoot); 
      } 
     } 

     private void handleDotStructure(AST dotStructureRoot) { 
      final String expression = ASTUtil.getPathText(dotStructureRoot); 
      final Object constant = ReflectHelper.getConstantValue(expression); 
      if (constant != null) { 
       dotStructureRoot.setFirstChild(null); 
       dotStructureRoot.setType(HqlTokenTypes.JAVA_CONSTANT); 
       dotStructureRoot.setText(expression); 
      } 
     } 
    } 

    @Override 
    public EntityGraphQueryHint getEntityGraphQueryHint() { 
     return entityGraphQueryHint; 
    } 

    @Override 
    public void setEntityGraphQueryHint(EntityGraphQueryHint entityGraphQueryHint) { 
     this.entityGraphQueryHint = entityGraphQueryHint; 
    } 
} 

如果按照碼流,你會發現,我只是修改方法private void generate(AST sqlAst) throws QueryException, RecognitionException並添加以下幾行:

//Hack: The distinct keywordis removed from the sql string to 
//avoid executing a distinct query in the DBMS when the distinct 
//is used in hql. 
sql = sql.replace("distinct", ""); 

我對此代碼所做的操作是從生成的SQL查詢中刪除distinct關鍵字。

創建上面的類之後,我加入了Hibernate配置文件中的以下行:

<property name="hibernate.query.factory_class">org.hibernate.hql.internal.ast.NoDistinctInSQLASTQueryTranslatorFactory</property> 

這行告訴Hibernate使用我的自定義類來解析HQL查詢和生成SQL查詢,沒有明顯的關鍵字。注意我在原來的HQL解析器所在的包中創建了我的自定義類。

相關問題