/*
 * 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.plan.exec.internal;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.ScrollMode;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.dialect.pagination.NoopLimitHandler;
import org.hibernate.engine.jdbc.ColumnNameCache;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.ResultSetWrapper;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext;
import org.hibernate.loader.plan.exec.spi.LoadQueryDetails;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.Type;

A superclass for loader implementations based on using LoadPlans.
Author:Gail Badner
See Also:
/** * A superclass for loader implementations based on using LoadPlans. * * @see org.hibernate.loader.entity.plan.EntityLoader * @see org.hibernate.loader.collection.plan.CollectionLoader * @author Gail Badner */
public abstract class AbstractLoadPlanBasedLoader { private static final CoreMessageLogger log = CoreLogging.messageLogger( AbstractLoadPlanBasedLoader.class ); private final SessionFactoryImplementor factory; private ColumnNameCache columnNameCache;
Params:
  • factory – The session factory
See Also:
/** * Constructs a {@link AbstractLoadPlanBasedLoader}. * * @param factory The session factory * @see SessionFactoryImplementor */
public AbstractLoadPlanBasedLoader( SessionFactoryImplementor factory) { this.factory = factory; } protected SessionFactoryImplementor getFactory() { return factory; } protected abstract LoadQueryDetails getStaticLoadQuery(); protected abstract int[] getNamedParameterLocs(String name); protected abstract void autoDiscoverTypes(ResultSet rs); protected List executeLoad( SharedSessionContractImplementor session, QueryParameters queryParameters, LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer) throws SQLException { final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>(); return executeLoad( session, queryParameters, loadQueryDetails, returnProxies, forcedResultTransformer, afterLoadActions ); } protected List executeLoad( SharedSessionContractImplementor session, QueryParameters queryParameters, LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer, List<AfterLoadAction> afterLoadActions) throws SQLException { final PersistenceContext persistenceContext = session.getPersistenceContext(); final boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); if ( queryParameters.isReadOnlyInitialized() ) { // The read-only/modifiable mode for the query was explicitly set. // Temporarily set the default read-only/modifiable setting to the query's setting. persistenceContext.setDefaultReadOnly( queryParameters.isReadOnly() ); } else { // The read-only/modifiable setting for the query was not initialized. // Use the default read-only/modifiable from the persistence context instead. queryParameters.setReadOnly( persistenceContext.isDefaultReadOnly() ); } persistenceContext.beforeLoad(); try { List results = null; final String sql = loadQueryDetails.getSqlStatement(); SqlStatementWrapper wrapper = null; try { wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); results = loadQueryDetails.getResultSetProcessor().extractResults( wrapper.getResultSet(), session, queryParameters, new NamedParameterContext() { @Override public int[] getNamedParameterLocations(String name) { return AbstractLoadPlanBasedLoader.this.getNamedParameterLocs( name ); } }, returnProxies, queryParameters.isReadOnly(), forcedResultTransformer, afterLoadActions ); } finally { if ( wrapper != null ) { session.getJdbcCoordinator().getResourceRegistry().release( wrapper.getResultSet(), wrapper.getStatement() ); session.getJdbcCoordinator().getResourceRegistry().release( wrapper.getStatement() ); session.getJdbcCoordinator().afterStatementExecution(); } persistenceContext.afterLoad(); } persistenceContext.initializeNonLazyCollections(); return results; } finally { // Restore the original default persistenceContext.setDefaultReadOnly( defaultReadOnlyOrig ); } } protected SqlStatementWrapper executeQueryStatement( final QueryParameters queryParameters, final boolean scroll, List<AfterLoadAction> afterLoadActions, final SharedSessionContractImplementor session) throws SQLException { return executeQueryStatement( getStaticLoadQuery().getSqlStatement(), queryParameters, scroll, afterLoadActions, session ); } protected SqlStatementWrapper executeQueryStatement( String sqlStatement, QueryParameters queryParameters, boolean scroll, List<AfterLoadAction> afterLoadActions, SharedSessionContractImplementor session) throws SQLException { // Processing query filters. queryParameters.processFilters( sqlStatement, session ); // Applying LIMIT clause. final LimitHandler limitHandler = getLimitHandler( queryParameters.getRowSelection() ); String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); // Adding locks and comments. sql = session.getJdbcServices().getJdbcEnvironment().getDialect() .addSqlHintOrComment( sql, queryParameters, session.getFactory().getSessionFactoryOptions().isCommentsEnabled() ); final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session ); return new SqlStatementWrapper( st, getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session ) ); }
Build LIMIT clause handler applicable for given selection criteria. Returns NoopLimitHandler delegate if dialect does not support LIMIT expression or processed query does not use pagination.
Params:
  • selection – Selection criteria.
Returns:LIMIT clause delegate.
/** * Build LIMIT clause handler applicable for given selection criteria. Returns {@link org.hibernate.dialect.pagination.NoopLimitHandler} delegate * if dialect does not support LIMIT expression or processed query does not use pagination. * * @param selection Selection criteria. * @return LIMIT clause delegate. */
protected LimitHandler getLimitHandler(RowSelection selection) { final LimitHandler limitHandler = getFactory().getDialect().getLimitHandler(); return LimitHelper.useLimit( limitHandler, selection ) ? limitHandler : NoopLimitHandler.INSTANCE; }
Obtain a PreparedStatement with all parameters pre-bound. Bind JDBC-style ? parameters, named parameters, and limit parameters.
/** * Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound. * Bind JDBC-style <tt>?</tt> parameters, named parameters, and * limit parameters. */
protected final PreparedStatement prepareQueryStatement( final String sql, final QueryParameters queryParameters, final LimitHandler limitHandler, final boolean scroll, final SharedSessionContractImplementor session) throws SQLException, HibernateException { final Dialect dialect = session.getJdbcServices().getJdbcEnvironment().getDialect(); final RowSelection selection = queryParameters.getRowSelection(); final boolean useLimit = LimitHelper.useLimit( limitHandler, selection ); final boolean hasFirstRow = LimitHelper.hasFirstRow( selection ); final boolean useLimitOffset = hasFirstRow && useLimit && limitHandler.supportsLimitOffset(); final boolean callable = queryParameters.isCallable(); final ScrollMode scrollMode = getScrollMode( scroll, hasFirstRow, useLimitOffset, queryParameters ); final PreparedStatement st = session.getJdbcCoordinator() .getStatementPreparer().prepareQueryStatement( sql, callable, scrollMode ); try { int col = 1; //TODO: can we limit stored procedures ?! col += limitHandler.bindLimitParametersAtStartOfQuery( selection, st, col ); if (callable) { col = dialect.registerResultSetOutParameter( (CallableStatement)st, col ); } col += bindParameterValues( st, queryParameters, col, session ); col += limitHandler.bindLimitParametersAtEndOfQuery( selection, st, col ); limitHandler.setMaxRows( selection, st ); if ( selection != null ) { if ( selection.getTimeout() != null ) { st.setQueryTimeout( selection.getTimeout() ); } if ( selection.getFetchSize() != null ) { st.setFetchSize( selection.getFetchSize() ); } } // handle lock timeout... final LockOptions lockOptions = queryParameters.getLockOptions(); if ( lockOptions != null ) { if ( lockOptions.getTimeOut() != LockOptions.WAIT_FOREVER ) { if ( !dialect.supportsLockTimeouts() ) { if ( log.isDebugEnabled() ) { log.debugf( "Lock timeout [%s] requested but dialect reported to not support lock timeouts", lockOptions.getTimeOut() ); } } else if ( dialect.isLockTimeoutParameterized() ) { st.setInt( col++, lockOptions.getTimeOut() ); } } } if ( log.isTraceEnabled() ) { log.tracev( "Bound [{0}] parameters total", col ); } } catch ( SQLException sqle ) { session.getJdbcCoordinator().getResourceRegistry().release( st ); session.getJdbcCoordinator().afterStatementExecution(); throw sqle; } catch ( HibernateException he ) { session.getJdbcCoordinator().getResourceRegistry().release( st ); session.getJdbcCoordinator().afterStatementExecution(); throw he; } return st; } protected ScrollMode getScrollMode(boolean scroll, boolean hasFirstRow, boolean useLimitOffSet, QueryParameters queryParameters) { final boolean canScroll = getFactory().getSettings().isScrollableResultSetsEnabled(); if ( canScroll ) { if ( scroll ) { return queryParameters.getScrollMode(); } if ( hasFirstRow && !useLimitOffSet ) { return ScrollMode.SCROLL_INSENSITIVE; } } return null; }
Bind all parameter values into the prepared statement in preparation for execution.
Params:
  • statement – The JDBC prepared statement
  • queryParameters – The encapsulation of the parameter values to be bound.
  • startIndex – The position from which to start binding parameter values.
  • session – The originating session.
Throws:
Returns:The number of JDBC bind positions actually bound during this method execution.
/** * Bind all parameter values into the prepared statement in preparation * for execution. * * @param statement The JDBC prepared statement * @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. */
protected int bindParameterValues( PreparedStatement statement, QueryParameters queryParameters, int startIndex, SharedSessionContractImplementor session) throws SQLException { int span = 0; span += bindPositionalParameters( statement, queryParameters, startIndex, session ); span += bindNamedParameters( statement, queryParameters.getNamedParameters(), startIndex + span, session ); return span; }
Bind positional parameter values to the JDBC prepared statement.

Positional parameters are those specified by JDBC-style ? parameters in the source query. It is (currently) expected that these come before any named parameters in the source query.
Params:
  • statement – The JDBC prepared statement
  • queryParameters – The encapsulation of the parameter values to be bound.
  • startIndex – The position from which to start binding parameter values.
  • session – The originating session.
Throws:
Returns:The number of JDBC bind positions actually bound during this method execution.
/** * Bind positional parameter values to the JDBC prepared statement. * <p/> * Positional parameters are those specified by JDBC-style ? parameters * in the source query. It is (currently) expected that these come * before any named parameters in the source query. * * @param statement The JDBC prepared statement * @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. * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types. */
protected int bindPositionalParameters( final PreparedStatement statement, final QueryParameters queryParameters, final int startIndex, final SharedSessionContractImplementor session) throws SQLException, HibernateException { final Object[] values = queryParameters.getFilteredPositionalParameterValues(); final Type[] types = queryParameters.getFilteredPositionalParameterTypes(); int span = 0; for ( int i = 0; i < values.length; i++ ) { types[i].nullSafeSet( statement, values[i], startIndex + span, session ); span += types[i].getColumnSpan( getFactory() ); } return span; }
Bind named parameters to the JDBC prepared statement.

This is a generic implementation, the problem being that in the general case we do not know enough information about the named parameters to perform this in a complete manner here. Thus this is generally overridden on subclasses allowing named parameters to apply the specific behavior. The most usual limitation here is that we need to assume the type span is always one...
Params:
  • statement – The JDBC prepared statement
  • namedParams – A map of parameter names to values
  • startIndex – The position from which to start binding parameter values.
  • session – The originating session.
Throws:
Returns:The number of JDBC bind positions actually bound during this method execution.
/** * Bind named parameters to the JDBC prepared statement. * <p/> * This is a generic implementation, the problem being that in the * general case we do not know enough information about the named * parameters to perform this in a complete manner here. Thus this * is generally overridden on subclasses allowing named parameters to * apply the specific behavior. The most usual limitation here is that * we need to assume the type span is always one... * * @param statement The JDBC prepared statement * @param namedParams A map of parameter names to values * @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. * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types. */
protected int bindNamedParameters( final PreparedStatement statement, final Map namedParams, final int startIndex, final SharedSessionContractImplementor session) throws SQLException, HibernateException { if ( namedParams != null ) { // assumes that types are all of span 1 final Iterator itr = namedParams.entrySet().iterator(); final boolean debugEnabled = log.isDebugEnabled(); int result = 0; while ( itr.hasNext() ) { final Map.Entry e = (Map.Entry) itr.next(); final String name = (String) e.getKey(); final TypedValue typedval = (TypedValue) e.getValue(); final int[] locs = getNamedParameterLocs( name ); for ( int loc : locs ) { if ( debugEnabled ) { log.debugf( "bindNamedParameters() %s -> %s [%s]", typedval.getValue(), name, loc + startIndex ); } typedval.getType().nullSafeSet( statement, typedval.getValue(), loc + startIndex, session ); } result += locs.length; } return result; } else { return 0; } }
Execute given PreparedStatement, advance to the first result and return SQL ResultSet.
/** * Execute given <tt>PreparedStatement</tt>, advance to the first result and return SQL <tt>ResultSet</tt>. */
protected final ResultSet getResultSet( final PreparedStatement st, final RowSelection selection, final LimitHandler limitHandler, final boolean autodiscovertypes, final SharedSessionContractImplementor session) throws SQLException, HibernateException { try { ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( st ); rs = wrapResultSetIfEnabled( rs , session ); if ( !limitHandler.supportsLimitOffset() || !LimitHelper.useLimit( limitHandler, selection ) ) { advance( rs, selection ); } if ( autodiscovertypes ) { autoDiscoverTypes( rs ); } return rs; } catch (SQLException | HibernateException ex) { session.getJdbcCoordinator().getResourceRegistry().release( st ); session.getJdbcCoordinator().afterStatementExecution(); throw ex; } }
Advance the cursor to the first required row of the ResultSet
/** * Advance the cursor to the first required row of the <tt>ResultSet</tt> */
protected void advance(final ResultSet rs, final RowSelection selection) throws SQLException { final int firstRow = LimitHelper.getFirstRow( selection ); if ( firstRow != 0 ) { if ( getFactory().getSettings().isScrollableResultSetsEnabled() ) { // we can go straight to the first required row rs.absolute( firstRow ); } else { // we need to step through the rows one row at a time (slow) for ( int m = 0; m < firstRow; m++ ) { rs.next(); } } } } private ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SharedSessionContractImplementor session) { if ( session.getFactory().getSessionFactoryOptions().isWrapResultSetsEnabled() ) { try { if ( log.isDebugEnabled() ) { log.debugf( "Wrapping result set [%s]", rs ); } ResultSetWrapper wrapper = session.getFactory() .getServiceRegistry() .getService( JdbcServices.class ) .getResultSetWrapper(); // synchronized to avoid multi-thread access issues // Apparently the comment about this needing synchronization was introduced when AbstractLoadPlanBasedLoader first appeared // in version control. Would need to investigate if it's still needed? synchronized ( this ) { return wrapper.wrap( rs, retreiveColumnNameToIndexCache( rs ) ); } } catch(SQLException e) { log.unableToWrapResultSet( e ); return rs; } } else { return rs; } } private ColumnNameCache retreiveColumnNameToIndexCache(ResultSet rs) throws SQLException { if ( columnNameCache == null ) { log.trace( "Building columnName->columnIndex cache" ); columnNameCache = new ColumnNameCache( rs.getMetaData().getColumnCount() ); } return columnNameCache; }
Wrapper class for Statement and associated ResultSet.
/** * Wrapper class for {@link java.sql.Statement} and associated {@link java.sql.ResultSet}. */
protected static class SqlStatementWrapper { private final Statement statement; private final ResultSet resultSet; private SqlStatementWrapper(Statement statement, ResultSet resultSet) { this.resultSet = resultSet; this.statement = statement; } public ResultSet getResultSet() { return resultSet; } public Statement getStatement() { return statement; } } }