首页 文章

为什么hibernate hql distinct导致sql在左连接上不同?

提问于
浏览
10

我有这个测试HQL:

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 distinct没有任何意义,使得查询比需要的慢 . 这与FAQ相反,后者提到在这种情况下hql distinct只是结果转换器的快捷方式:

session.createQuery(“从Order o left join fetch o.lineItems中选择不同的o”) . list();看起来您在这里使用SQL DISTINCT关键字 . 当然,这不是SQL,这是HQL . 在这种情况下,这个不同只是结果转换器的快捷方式 . 是的,在其他情况下,HQL distinct将直接转换为SQL DISTINCT . 不是在这种情况下:您无法在SQL级别过滤掉重复项,产品/连接的本质禁止这样做 - 您需要重复项或者您没有获得所需的所有数据 .

谢谢

2 回答

  • 3

    仔细看看hibernate生成的sql语句 - 是的它确实使用了“distinct”关键字但不是我认为你期望它的方式(或者Hibernate FAQ暗示的方式),即返回一组“不同的”或“独特的”订单 .

    它不使用distinct关键字来返回不同的顺序,因为考虑到您指定的连接,在SQL查询中没有意义 .

    生成的sql集仍然需要由ResultTransformer处理,因为sql set显然包含重复的订单 . 这就是为什么他们说HQL distinct关键字不直接映射到SQL distinct关键字 .

  • 1

    我有完全相同的问题,我认为这是一个Hibernate问题(不是一个错误,因为代码不会失败) . 但是,我必须深入挖掘以确保它是一个问题 .

    Hibernate(至少在版本4中,它是我正在处理我的项目的版本,特别是4.3.11)使用SPI的概念,长话短说:它就像一个扩展或修改框架的API .

    我利用这个功能来替换类 org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory (这个类由Hibernate调用并委托生成SQL查询的工作)和 org.hibernate.hql.internal.ast.QueryTranslatorImpl (这是一种内部类,由 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 (pgmjsd@sourceforge.net)
     */
    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查询并生成没有distinct关键字的SQL查询 . 请注意,我在原始HQL解析器所在的同一个包中创建了自定义类 .

相关问题