org.hibernate.loader.hql.QueryLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.loader.hql.QueryLoader.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.loader.hql;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.QueryException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.hql.internal.HolderInstantiator;
import org.hibernate.hql.internal.ast.QueryTranslatorImpl;
import org.hibernate.hql.internal.ast.tree.AggregatedSelectExpression;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.hql.spi.NamedParameterInformation;
import org.hibernate.hql.spi.ParameterInformation;
import org.hibernate.internal.IteratorImpl;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.BasicLoader;
import org.hibernate.loader.internal.AliasConstantsHelper;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.param.ParameterSpecification;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.Lockable;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.query.spi.ScrollableResultsImplementor;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

/**
 * A delegate that implements the Loader part of QueryTranslator.
 *
 * @author josh
 */
public class QueryLoader extends BasicLoader {

    /**
     * The query translator that is delegating to this object.
     */
    private QueryTranslatorImpl queryTranslator;

    private Queryable[] entityPersisters;
    private String[] entityAliases;
    private String[] sqlAliases;
    private String[] sqlAliasSuffixes;
    private boolean[] includeInSelect;

    private String[] collectionSuffixes;

    private boolean hasScalars;
    private String[][] scalarColumnNames;
    //private Type[] sqlResultTypes;
    private Type[] queryReturnTypes;

    private final Map<String, String> sqlAliasByEntityAlias = new HashMap<>(8);

    private EntityType[] ownerAssociationTypes;
    private int[] owners;
    private boolean[] entityEagerPropertyFetches;

    private int[] collectionOwners;
    private QueryableCollection[] collectionPersisters;

    private int selectLength;

    private AggregatedSelectExpression aggregatedSelectExpression;
    private String[] queryReturnAliases;

    private LockMode[] defaultLockModes;

    /**
     * Creates a new Loader implementation.
     *
     * @param queryTranslator The query translator that is the delegator.
     * @param factory The factory from which this loader is being created.
     * @param selectClause The AST representing the select clause for loading.
     */
    public QueryLoader(final QueryTranslatorImpl queryTranslator, final SessionFactoryImplementor factory,
            final SelectClause selectClause) {
        super(factory);
        this.queryTranslator = queryTranslator;
        initialize(selectClause);
        postInstantiate();
    }

    private void initialize(SelectClause selectClause) {

        List fromElementList = selectClause.getFromElementsForLoad();

        hasScalars = selectClause.isScalarSelect();
        scalarColumnNames = selectClause.getColumnNames();
        //sqlResultTypes = selectClause.getSqlResultTypes();
        queryReturnTypes = selectClause.getQueryReturnTypes();

        aggregatedSelectExpression = selectClause.getAggregatedSelectExpression();
        queryReturnAliases = selectClause.getQueryReturnAliases();

        List collectionFromElements = selectClause.getCollectionFromElements();
        if (collectionFromElements != null && collectionFromElements.size() != 0) {
            int length = collectionFromElements.size();
            collectionPersisters = new QueryableCollection[length];
            collectionOwners = new int[length];
            collectionSuffixes = new String[length];
            for (int i = 0; i < length; i++) {
                FromElement collectionFromElement = (FromElement) collectionFromElements.get(i);
                collectionPersisters[i] = collectionFromElement.getQueryableCollection();
                collectionOwners[i] = fromElementList.indexOf(collectionFromElement.getOrigin());
                //            collectionSuffixes[i] = collectionFromElement.getColumnAliasSuffix();
                //            collectionSuffixes[i] = Integer.toString( i ) + "_";
                collectionSuffixes[i] = collectionFromElement.getCollectionSuffix();
            }
        }

        int size = fromElementList.size();
        entityPersisters = new Queryable[size];
        entityEagerPropertyFetches = new boolean[size];
        entityAliases = new String[size];
        sqlAliases = new String[size];
        sqlAliasSuffixes = new String[size];
        includeInSelect = new boolean[size];
        owners = new int[size];
        ownerAssociationTypes = new EntityType[size];

        for (int i = 0; i < size; i++) {
            final FromElement element = (FromElement) fromElementList.get(i);
            entityPersisters[i] = (Queryable) element.getEntityPersister();

            if (entityPersisters[i] == null) {
                throw new IllegalStateException("No entity persister for " + element.toString());
            }

            entityEagerPropertyFetches[i] = element.isAllPropertyFetch();
            sqlAliases[i] = element.getTableAlias();
            entityAliases[i] = element.getClassAlias();
            sqlAliasByEntityAlias.put(entityAliases[i], sqlAliases[i]);
            // TODO should we just collect these like with the collections above?
            sqlAliasSuffixes[i] = (size == 1) ? "" : AliasConstantsHelper.get(i);
            //         sqlAliasSuffixes[i] = element.getColumnAliasSuffix();
            includeInSelect[i] = !element.isFetch();
            if (includeInSelect[i]) {
                selectLength++;
            }

            owners[i] = -1; //by default
            if (element.isFetch()) {
                //noinspection StatementWithEmptyBody
                if (element.isCollectionJoin() || element.getQueryableCollection() != null) {
                    // This is now handled earlier in this method.
                } else if (element.getDataType().isEntityType()) {
                    EntityType entityType = (EntityType) element.getDataType();
                    if (entityType.isOneToOne()) {
                        owners[i] = fromElementList.indexOf(element.getOrigin());
                    }
                    ownerAssociationTypes[i] = entityType;
                }
            }
        }

        //NONE, because its the requested lock mode, not the actual! 
        defaultLockModes = ArrayHelper.fillArray(LockMode.NONE, size);
    }

    public AggregatedSelectExpression getAggregatedSelectExpression() {
        return aggregatedSelectExpression;
    }

    // -- Loader implementation --

    public final void validateScrollability() throws HibernateException {
        queryTranslator.validateScrollability();
    }

    @Override
    protected boolean needsFetchingScroll() {
        return queryTranslator.containsCollectionFetches();
    }

    @Override
    public Loadable[] getEntityPersisters() {
        return entityPersisters;
    }

    @Override
    public String[] getAliases() {
        return sqlAliases;
    }

    public String[] getSqlAliasSuffixes() {
        return sqlAliasSuffixes;
    }

    @Override
    public String[] getSuffixes() {
        return getSqlAliasSuffixes();
    }

    @Override
    public String[] getCollectionSuffixes() {
        return collectionSuffixes;
    }

    @Override
    protected String getQueryIdentifier() {
        return queryTranslator.getQueryIdentifier();
    }

    /**
     * The SQL query string to be called.
     */
    @Override
    public String getSQLString() {
        return queryTranslator.getSQLString();
    }

    /**
     * An (optional) persister for a collection to be initialized; only collection loaders
     * return a non-null value
     */
    @Override
    protected CollectionPersister[] getCollectionPersisters() {
        return collectionPersisters;
    }

    @Override
    protected int[] getCollectionOwners() {
        return collectionOwners;
    }

    @Override
    protected boolean[] getEntityEagerPropertyFetches() {
        return entityEagerPropertyFetches;
    }

    /**
     * An array of indexes of the entity that owns a one-to-one association
     * to the entity at the given index (-1 if there is no "owner")
     */
    @Override
    protected int[] getOwners() {
        return owners;
    }

    @Override
    protected EntityType[] getOwnerAssociationTypes() {
        return ownerAssociationTypes;
    }

    // -- Loader overrides --
    @Override
    protected boolean isSubselectLoadingEnabled() {
        return hasSubselectLoadableCollections();
    }

    /**
     * @param lockOptions a collection of lock modes specified dynamically via the Query interface
     */
    @Override
    protected LockMode[] getLockModes(LockOptions lockOptions) {
        if (lockOptions == null) {
            return defaultLockModes;
        }

        if (lockOptions.getAliasLockCount() == 0
                && (lockOptions.getLockMode() == null || LockMode.NONE.equals(lockOptions.getLockMode()))) {
            return defaultLockModes;
        }

        // unfortunately this stuff can't be cached because
        // it is per-invocation, not constant for the
        // QueryTranslator instance

        LockMode[] lockModesArray = new LockMode[entityAliases.length];
        for (int i = 0; i < entityAliases.length; i++) {
            LockMode lockMode = lockOptions.getEffectiveLockMode(entityAliases[i]);
            if (lockMode == null) {
                //NONE, because its the requested lock mode, not the actual!
                lockMode = LockMode.NONE;
            }
            lockModesArray[i] = lockMode;
        }

        return lockModesArray;
    }

    @Override
    protected String applyLocks(String sql, QueryParameters parameters, Dialect dialect,
            List<AfterLoadAction> afterLoadActions) throws QueryException {
        // can't cache this stuff either (per-invocation)
        // we are given a map of user-alias -> lock mode
        // create a new map of sql-alias -> lock mode

        final LockOptions lockOptions = parameters.getLockOptions();

        if (lockOptions == null
                || (lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0)) {
            return sql;
        }

        // user is request locking, lets see if we can apply locking directly to the SQL...

        //       some dialects wont allow locking with paging...
        if (shouldUseFollowOnLocking(parameters, dialect, afterLoadActions)) {
            return sql;
        }

        //      there are other conditions we might want to add here, such as checking the result types etc
        //      but those are better served after we have redone the SQL generation to use ASTs.

        // we need both the set of locks and the columns to reference in locks
        // as the ultimate output of this section...
        final LockOptions locks = new LockOptions(lockOptions.getLockMode());
        final Map<String, String[]> keyColumnNames = dialect.forUpdateOfColumns() ? new HashMap<>() : null;

        locks.setScope(lockOptions.getScope());
        locks.setTimeOut(lockOptions.getTimeOut());

        for (Map.Entry<String, String> entry : sqlAliasByEntityAlias.entrySet()) {
            final String userAlias = entry.getKey();
            final String drivingSqlAlias = entry.getValue();
            if (drivingSqlAlias == null) {
                throw new IllegalArgumentException("could not locate alias to apply lock mode : " + userAlias);
            }
            // at this point we have (drivingSqlAlias) the SQL alias of the driving table
            // corresponding to the given user alias.  However, the driving table is not
            // (necessarily) the table against which we want to apply locks.  Mainly,
            // the exception case here is joined-subclass hierarchies where we instead
            // want to apply the lock against the root table (for all other strategies,
            // it just happens that driving and root are the same).
            final QueryNode select = (QueryNode) queryTranslator.getSqlAST();
            final Lockable drivingPersister = (Lockable) select.getFromClause()
                    .findFromElementByUserOrSqlAlias(userAlias, drivingSqlAlias).getQueryable();
            final String sqlAlias = drivingPersister.getRootTableAlias(drivingSqlAlias);

            final LockMode effectiveLockMode = lockOptions.getEffectiveLockMode(userAlias);
            locks.setAliasSpecificLockMode(sqlAlias, effectiveLockMode);

            if (keyColumnNames != null) {
                keyColumnNames.put(sqlAlias, drivingPersister.getRootTableIdentifierColumnNames());
            }
        }

        // apply the collected locks and columns
        return dialect.applyLocksToSql(sql, locks, keyColumnNames);
    }

    @Override
    protected void applyPostLoadLocks(Object[] row, LockMode[] lockModesArray,
            SharedSessionContractImplementor session) {
        // todo : scalars???
        //      if ( row.length != lockModesArray.length ) {
        //         return;
        //      }
        //
        //      for ( int i = 0; i < lockModesArray.length; i++ ) {
        //         if ( LockMode.OPTIMISTIC_FORCE_INCREMENT.equals( lockModesArray[i] ) ) {
        //            final EntityEntry pcEntry =
        //         }
        //         else if ( LockMode.PESSIMISTIC_FORCE_INCREMENT.equals( lockModesArray[i] ) ) {
        //
        //         }
        //      }
    }

    @Override
    protected boolean upgradeLocks() {
        return true;
    }

    private boolean hasSelectNew() {
        return aggregatedSelectExpression != null && aggregatedSelectExpression.getResultTransformer() != null;
    }

    @Override
    protected String[] getResultRowAliases() {
        return queryReturnAliases;
    }

    @Override
    protected ResultTransformer resolveResultTransformer(ResultTransformer resultTransformer) {
        final ResultTransformer implicitResultTransformer = aggregatedSelectExpression == null ? null
                : aggregatedSelectExpression.getResultTransformer();
        return HolderInstantiator.resolveResultTransformer(implicitResultTransformer, resultTransformer);
    }

    @Override
    protected boolean[] includeInResultRow() {
        boolean[] includeInResultTuple = includeInSelect;
        if (hasScalars) {
            includeInResultTuple = new boolean[queryReturnTypes.length];
            Arrays.fill(includeInResultTuple, true);
        }
        return includeInResultTuple;
    }

    @Override
    protected Object getResultColumnOrRow(Object[] row, ResultTransformer transformer, ResultSet rs,
            SharedSessionContractImplementor session) throws SQLException, HibernateException {

        Object[] resultRow = getResultRow(row, rs, session);
        boolean hasTransform = hasSelectNew() || transformer != null;
        return (!hasTransform && resultRow.length == 1 ? resultRow[0] : resultRow);
    }

    @Override
    protected Object[] getResultRow(Object[] row, ResultSet rs, SharedSessionContractImplementor session)
            throws SQLException, HibernateException {
        Object[] resultRow;
        if (hasScalars) {
            String[][] scalarColumns = scalarColumnNames;
            int queryCols = queryReturnTypes.length;
            resultRow = new Object[queryCols];
            for (int i = 0; i < queryCols; i++) {
                resultRow[i] = queryReturnTypes[i].nullSafeGet(rs, scalarColumns[i], session, null);
            }
        } else {
            resultRow = toResultRow(row);
        }
        return resultRow;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException {
        // meant to handle dynamic instantiation queries...
        HolderInstantiator holderInstantiator = buildHolderInstantiator(resultTransformer);
        if (holderInstantiator.isRequired()) {
            for (int i = 0; i < results.size(); i++) {
                Object[] row = (Object[]) results.get(i);
                Object result = holderInstantiator.instantiate(row);
                results.set(i, result);
            }

            if (!hasSelectNew() && resultTransformer != null) {
                return resultTransformer.transformList(results);
            } else {
                return results;
            }
        } else {
            return results;
        }
    }

    private HolderInstantiator buildHolderInstantiator(ResultTransformer queryLocalResultTransformer) {
        final ResultTransformer implicitResultTransformer = aggregatedSelectExpression == null ? null
                : aggregatedSelectExpression.getResultTransformer();
        return HolderInstantiator.getHolderInstantiator(implicitResultTransformer, queryLocalResultTransformer,
                queryReturnAliases);
    }
    // --- Query translator methods ---

    public List list(SharedSessionContractImplementor session, QueryParameters queryParameters)
            throws HibernateException {
        checkQuery(queryParameters);
        return list(session, queryParameters, queryTranslator.getQuerySpaces(), queryReturnTypes);
    }

    private void checkQuery(QueryParameters queryParameters) {
        if (hasSelectNew() && queryParameters.getResultTransformer() != null) {
            throw new QueryException("ResultTransformer is not allowed for 'select new' queries.");
        }
    }

    public Iterator iterate(QueryParameters queryParameters, EventSource session) throws HibernateException {
        checkQuery(queryParameters);
        final StatisticsImplementor statistics = session.getFactory().getStatistics();
        final boolean stats = statistics.isStatisticsEnabled();
        long startTime = 0;
        if (stats) {
            startTime = System.nanoTime();
        }

        try {
            if (queryParameters.isCallable()) {
                throw new QueryException("iterate() not supported for callable statements");
            }
            final SqlStatementWrapper wrapper = executeQueryStatement(queryParameters, false,
                    Collections.emptyList(), session);
            final ResultSet rs = wrapper.getResultSet();
            final PreparedStatement st = (PreparedStatement) wrapper.getStatement();
            final Iterator result = new IteratorImpl(rs, st, session, queryParameters.isReadOnly(session),
                    queryReturnTypes, queryTranslator.getColumnNames(),
                    buildHolderInstantiator(queryParameters.getResultTransformer()));

            if (stats) {
                final long endTime = System.nanoTime();
                final long milliseconds = TimeUnit.MILLISECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS);
                statistics.queryExecuted(
                        //                  "HQL: " + queryTranslator.getQueryString(),
                        getQueryIdentifier(), 0, milliseconds);
            }

            return result;

        } catch (SQLException sqle) {
            throw session.getJdbcServices().getSqlExceptionHelper().convert(sqle,
                    "could not execute query using iterate", getSQLString());
        }

    }

    public ScrollableResultsImplementor scroll(final QueryParameters queryParameters,
            final SharedSessionContractImplementor session) throws HibernateException {
        checkQuery(queryParameters);
        return scroll(queryParameters, queryReturnTypes,
                buildHolderInstantiator(queryParameters.getResultTransformer()), session);
    }

    // -- Implementation private methods --

    private Object[] toResultRow(Object[] row) {
        if (selectLength == row.length) {
            return row;
        } else {
            Object[] result = new Object[selectLength];
            int j = 0;
            for (int i = 0; i < row.length; i++) {
                if (includeInSelect[i]) {
                    result[j++] = row[i];
                }
            }
            return result;
        }
    }

    /**
     * Returns the locations of all occurrences of the named parameter.
     */
    @Override
    public int[] getNamedParameterLocs(String name) throws QueryException {
        ParameterInformation info = queryTranslator.getParameterTranslations().getNamedParameterInformation(name);
        if (info == null) {
            try {
                info = queryTranslator.getParameterTranslations()
                        .getPositionalParameterInformation(Integer.parseInt(name));
            } catch (Exception ignore) {
            }
        }

        if (info == null) {
            throw new QueryException("Unrecognized parameter label : " + name);
        }

        return info.getSourceLocations();
    }

    /**
     * We specifically override this method here, because in general we know much more
     * about the parameters and their appropriate bind positions here then we do in
     * our super because we track them explicitly here through the ParameterSpecification
     * interface.
     *
     * @param queryParameters The encapsulation of the parameter values to be bound.
     * @param startIndex The position from which to start binding parameter values.
     * @param session The originating session.
     *
     * @return The number of JDBC bind positions actually bound during this method execution.
     *
     * @throws SQLException Indicates problems performing the binding.
     */
    @Override
    protected int bindParameterValues(final PreparedStatement statement, final QueryParameters queryParameters,
            final int startIndex, final SharedSessionContractImplementor session) throws SQLException {
        int position = startIndex;
        List<ParameterSpecification> parameterSpecs = queryTranslator.getCollectedParameterSpecifications();
        for (ParameterSpecification spec : parameterSpecs) {
            position += spec.bind(statement, queryParameters, session, position);
        }
        return position - startIndex;
    }
}