HHH-8276 - Integrate LoadPlanAdvisor into UniqueEntityLoader (PoC)

This commit is contained in:
Steve Ebersole 2013-05-29 08:26:28 -05:00
parent 290133158c
commit 456d61bd4e
5 changed files with 1208 additions and 1 deletions

View File

@ -48,7 +48,7 @@ public abstract class BatchingEntityLoaderBuilder {
return DynamicBatchingEntityLoaderBuilder.INSTANCE;
}
default: {
return LegacyBatchingEntityLoaderBuilder.INSTANCE;
return org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder.INSTANCE;
}
}
}

View File

@ -0,0 +1,769 @@
package org.hibernate.loader.entity.plan;
import java.io.Serializable;
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.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.AssertionFailure;
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.spi.LoadQueryInfluencers;
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.SessionImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.loader.entity.UniqueEntityLoader;
import org.hibernate.loader.internal.EntityLoadQueryBuilderImpl;
import org.hibernate.loader.internal.LoadQueryAliasResolutionContextImpl;
import org.hibernate.loader.internal.ResultSetProcessorImpl;
import org.hibernate.loader.plan.internal.SingleRootReturnLoadPlanBuilderStrategy;
import org.hibernate.loader.plan.spi.LoadPlan;
import org.hibernate.loader.plan.spi.build.LoadPlanBuilder;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.loader.spi.LoadQueryAliasResolutionContext;
import org.hibernate.loader.spi.NamedParameterContext;
import org.hibernate.loader.spi.NoOpLoadPlanAdvisor;
import org.hibernate.loader.spi.ResultSetProcessor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.Type;
/**
* A UniqueEntityLoader implementation based on using LoadPlans
*
* @author Steve Ebersole
*/
public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityLoader {
private static final CoreMessageLogger log = CoreLogging.messageLogger( AbstractLoadPlanBasedEntityLoader.class );
private final SessionFactoryImplementor factory;
private final OuterJoinLoadable entityPersister;
private final Type uniqueKeyType;
private final String entityName;
private final LoadPlan plan;
private final String staticSql;
private final LoadQueryAliasResolutionContext staticAliasResolutionContext;
private final ResultSetProcessor staticResultSetProcessor;
private ColumnNameCache columnNameCache;
public AbstractLoadPlanBasedEntityLoader(
OuterJoinLoadable entityPersister,
Type uniqueKeyType,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
this.factory = factory;
this.uniqueKeyType = uniqueKeyType;
this.entityName = entityPersister.getEntityName();
this.entityPersister = entityPersister;
final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy(
factory,
loadQueryInfluencers
);
this.plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister );
this.staticAliasResolutionContext = buildAliasResolutionContext( plan, factory );
this.staticSql = generateStaticSql( plan, staticAliasResolutionContext, factory, loadQueryInfluencers );
this.staticResultSetProcessor = generateStaticResultSetProcessor( plan );
}
protected LoadQueryAliasResolutionContext buildAliasResolutionContext(LoadPlan plan, SessionFactoryImplementor factory) {
return new LoadQueryAliasResolutionContextImpl(
factory,
0,
Collections.singletonMap( plan.getReturns().get( 0 ), new String[] {"abc"} )
);
}
protected String generateStaticSql(
LoadPlan plan,
LoadQueryAliasResolutionContext aliasResolutionContext,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
return new EntityLoadQueryBuilderImpl( loadQueryInfluencers, plan ).generateSql(
1,
factory,
aliasResolutionContext
);
}
protected ResultSetProcessor generateStaticResultSetProcessor(LoadPlan plan) {
return new ResultSetProcessorImpl( plan );
}
protected SessionFactoryImplementor getFactory() {
return factory;
}
protected String getSqlStatement() {
return staticSql;
}
protected String getEntityName() {
return entityName;
}
/**
* Called by wrappers that batch load entities
* @param persister only needed for logging
* @param lockOptions
*/
public final List loadEntityBatch(
final SessionImplementor session,
final Serializable[] ids,
final Type idType,
final Object optionalObject,
final String optionalEntityName,
final Serializable optionalId,
final EntityPersister persister,
LockOptions lockOptions) throws HibernateException {
if ( log.isDebugEnabled() ) {
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, getFactory() ) );
}
final Type[] types = new Type[ids.length];
Arrays.fill( types, idType );
List result;
try {
final QueryParameters qp = new QueryParameters();
qp.setPositionalParameterTypes( types );
qp.setPositionalParameterValues( ids );
qp.setOptionalObject( optionalObject );
qp.setOptionalEntityName( optionalEntityName );
qp.setOptionalId( optionalId );
qp.setLockOptions( lockOptions );
result = executeLoad(
session,
qp,
staticSql,
staticResultSetProcessor,
staticAliasResolutionContext,
false,
null
);
}
catch ( SQLException sqle ) {
throw factory.getSQLExceptionHelper().convert(
sqle,
"could not load an entity batch: " + MessageHelper.infoString( entityPersister, ids, getFactory() ),
getSqlStatement()
);
}
log.debug( "Done entity batch load" );
return result;
}
@Override
@Deprecated
public Object load(Serializable id, Object optionalObject, SessionImplementor session) throws HibernateException {
return load( id, optionalObject, session, LockOptions.NONE );
}
@Override
public Object load(Serializable id, Object optionalObject, SessionImplementor session, LockOptions lockOptions) {
Object result = null;
try {
final QueryParameters qp = new QueryParameters();
qp.setPositionalParameterTypes( new Type[] { entityPersister.getIdentifierType() } );
qp.setPositionalParameterValues( new Object[] { id } );
qp.setOptionalObject( optionalObject );
qp.setOptionalEntityName( entityPersister.getEntityName() );
qp.setOptionalId( id );
qp.setLockOptions( lockOptions );
final List results = executeLoad(
session,
qp,
staticSql,
staticResultSetProcessor,
staticAliasResolutionContext,
false,
null
);
result = extractEntityResult( results );
}
catch ( SQLException sqle ) {
throw factory.getSQLExceptionHelper().convert(
sqle,
"could not load an entity: " + MessageHelper.infoString(
entityPersister,
id,
entityPersister.getIdentifierType(),
factory
),
getSqlStatement()
);
}
log.debugf( "Done entity load : %s#%s", getEntityName(), id );
return result;
}
protected List executeLoad(
SessionImplementor session,
QueryParameters queryParameters,
String sql,
ResultSetProcessor resultSetProcessor,
LoadQueryAliasResolutionContext aliasResolutionContext,
boolean returnProxies,
ResultTransformer forcedResultTransformer) throws SQLException {
final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
return executeLoad(
session,
queryParameters,
sql,
resultSetProcessor,
aliasResolutionContext,
returnProxies,
forcedResultTransformer,
afterLoadActions
);
}
protected List executeLoad(
SessionImplementor session,
QueryParameters queryParameters,
String sql,
ResultSetProcessor resultSetProcessor,
LoadQueryAliasResolutionContext aliasResolutionContext,
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;
try {
final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
results = resultSetProcessor.extractResults(
// todo : hook in the JPA 2.1 entity graph advisor
NoOpLoadPlanAdvisor.INSTANCE,
wrapper.getResultSet(),
session,
queryParameters,
new NamedParameterContext() {
@Override
public int[] getNamedParameterLocations(String name) {
return AbstractLoadPlanBasedEntityLoader.this.getNamedParameterLocs( name );
}
},
aliasResolutionContext,
returnProxies,
queryParameters.isReadOnly(),
forcedResultTransformer,
afterLoadActions
);
}
finally {
persistenceContext.afterLoad();
}
persistenceContext.initializeNonLazyCollections();
return results;
}
finally {
// Restore the original default
persistenceContext.setDefaultReadOnly( defaultReadOnlyOrig );
}
}
protected Object extractEntityResult(List results) {
if ( results.size() == 0 ) {
return null;
}
else if ( results.size() == 1 ) {
return results.get( 0 );
}
else {
final Object row = results.get( 0 );
if ( row.getClass().isArray() ) {
// the logical type of the result list is List<Object[]>. See if the contained
// array contains just one element, and return that if so
final Object[] rowArray = (Object[]) row;
if ( rowArray.length == 1 ) {
return rowArray[0];
}
}
else {
return row;
}
}
throw new HibernateException( "Unable to interpret given query results in terms of a load-entity query" );
}
protected Object doQueryAndLoadEntity(
SessionImplementor session,
QueryParameters queryParameters,
String sql,
ResultSetProcessor resultSetProcessor,
LoadQueryAliasResolutionContext aliasResolutionContext,
boolean returnProxies,
ResultTransformer forcedResultTransformer) throws SQLException {
final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, false, afterLoadActions, session );
try {
final List results = resultSetProcessor.extractResults(
NoOpLoadPlanAdvisor.INSTANCE,
wrapper.getResultSet(),
session,
queryParameters,
new NamedParameterContext() {
@Override
public int[] getNamedParameterLocations(String name) {
return AbstractLoadPlanBasedEntityLoader.this.getNamedParameterLocs( name );
}
},
staticAliasResolutionContext,
returnProxies,
queryParameters.isReadOnly(),
forcedResultTransformer,
afterLoadActions
);
if ( results.size() == 0 ) {
return null;
}
else if ( results.size() == 1 ) {
return results.get( 0 );
}
else {
final Object row = results.get( 0 );
if ( row.getClass().isArray() ) {
// the logical type of the result list is List<Object[]>. See if the contained
// array contains just one element, and return that if so
final Object[] rowArray = (Object[]) row;
if ( rowArray.length == 1 ) {
return rowArray[0];
}
}
else {
return row;
}
}
throw new HibernateException( "Unable to interpret given query results in terms of a load-entity query" );
}
finally {
session.getTransactionCoordinator().getJdbcCoordinator().release( wrapper.getStatement() );
}
}
protected SqlStatementWrapper executeQueryStatement(
final QueryParameters queryParameters,
final boolean scroll,
List<AfterLoadAction> afterLoadActions,
final SessionImplementor session) throws SQLException {
return executeQueryStatement( getSqlStatement(), queryParameters, scroll, afterLoadActions, session );
}
protected SqlStatementWrapper executeQueryStatement(
String sqlStatement,
QueryParameters queryParameters,
boolean scroll,
List<AfterLoadAction> afterLoadActions,
SessionImplementor session) throws SQLException {
// Processing query filters.
queryParameters.processFilters( sqlStatement, session );
// Applying LIMIT clause.
final LimitHandler limitHandler = getLimitHandler(
queryParameters.getFilteredSQL(),
queryParameters.getRowSelection()
);
String sql = limitHandler.getProcessedSql();
// Adding locks and comments.
sql = preprocessSQL( sql, queryParameters, getFactory().getDialect(), afterLoadActions );
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 {@link org.hibernate.dialect.pagination.NoopLimitHandler} delegate
* if dialect does not support LIMIT expression or processed query does not use pagination.
*
* @param sql Query string.
* @param selection Selection criteria.
* @return LIMIT clause delegate.
*/
protected LimitHandler getLimitHandler(String sql, RowSelection selection) {
final LimitHandler limitHandler = getFactory().getDialect().buildLimitHandler( sql, selection );
return LimitHelper.useLimit( limitHandler, selection ) ? limitHandler : new NoopLimitHandler( sql, selection );
}
private String preprocessSQL(
String sql,
QueryParameters queryParameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) {
return getFactory().getSettings().isCommentsEnabled()
? prependComment( sql, queryParameters )
: sql;
}
private String prependComment(String sql, QueryParameters parameters) {
final String comment = parameters.getComment();
if ( comment == null ) {
return sql;
}
else {
return "/* " + comment + " */ " + sql;
}
}
/**
* 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 SessionImplementor session) throws SQLException, HibernateException {
final Dialect dialect = getFactory().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.getTransactionCoordinator().getJdbcCoordinator()
.getStatementPreparer().prepareQueryStatement( sql, callable, scrollMode );
try {
int col = 1;
//TODO: can we limit stored procedures ?!
col += limitHandler.bindLimitParametersAtStartOfQuery( st, col );
if (callable) {
col = dialect.registerResultSetOutParameter( (CallableStatement)st, col );
}
col += bindParameterValues( st, queryParameters, col, session );
col += limitHandler.bindLimitParametersAtEndOfQuery( st, col );
limitHandler.setMaxRows( 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.getTransactionCoordinator().getJdbcCoordinator().release( st );
throw sqle;
}
catch ( HibernateException he ) {
session.getTransactionCoordinator().getJdbcCoordinator().release( st );
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.
*
* @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,
SessionImplementor 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.
* <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 SessionImplementor 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.
* <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 SessionImplementor 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;
}
}
public int[] getNamedParameterLocs(String name) {
throw new AssertionFailure("no named parameters");
}
/**
* 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 SessionImplementor session)
throws SQLException, HibernateException {
try {
ResultSet rs = session.getTransactionCoordinator().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 sqle ) {
session.getTransactionCoordinator().getJdbcCoordinator().release( st );
throw sqle;
}
}
/**
* 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();
}
}
}
}
protected void autoDiscoverTypes(ResultSet rs) {
throw new AssertionFailure("Auto discover types not supported in this loader");
}
private synchronized ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SessionImplementor session) {
// synchronized to avoid multi-thread access issues; defined as method synch to avoid
// potential deadlock issues due to nature of code.
if ( session.getFactory().getSettings().isWrapResultSetsEnabled() ) {
try {
if ( log.isDebugEnabled() ) {
log.debugf( "Wrapping result set [%s]", rs );
}
return session.getFactory()
.getJdbcServices()
.getResultSetWrapper().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 {@link java.sql.Statement} and associated {@link 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;
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.entity.plan;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.jboss.logging.Logger;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.loader.Loader;
import org.hibernate.loader.entity.UniqueEntityLoader;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.Type;
/**
* The base contract for loaders capable of performing batch-fetch loading of entities using multiple primary key
* values in the SQL <tt>WHERE</tt> clause.
*
* @author Gavin King
* @author Steve Ebersole
*
* @see org.hibernate.loader.entity.BatchingEntityLoaderBuilder
* @see org.hibernate.loader.entity.UniqueEntityLoader
*/
public abstract class BatchingEntityLoader implements UniqueEntityLoader {
private static final Logger log = Logger.getLogger( BatchingEntityLoader.class );
private final EntityPersister persister;
public BatchingEntityLoader(EntityPersister persister) {
this.persister = persister;
}
public EntityPersister persister() {
return persister;
}
@Override
@Deprecated
public Object load(Serializable id, Object optionalObject, SessionImplementor session) {
return load( id, optionalObject, session, LockOptions.NONE );
}
protected QueryParameters buildQueryParameters(
Serializable id,
Serializable[] ids,
Object optionalObject,
LockOptions lockOptions) {
Type[] types = new Type[ids.length];
Arrays.fill( types, persister().getIdentifierType() );
QueryParameters qp = new QueryParameters();
qp.setPositionalParameterTypes( types );
qp.setPositionalParameterValues( ids );
qp.setOptionalObject( optionalObject );
qp.setOptionalEntityName( persister().getEntityName() );
qp.setOptionalId( id );
qp.setLockOptions( lockOptions );
return qp;
}
protected Object getObjectFromList(List results, Serializable id, SessionImplementor session) {
for ( Object obj : results ) {
final boolean equal = persister.getIdentifierType().isEqual(
id,
session.getContextEntityIdentifier( obj ),
session.getFactory()
);
if ( equal ) {
return obj;
}
}
return null;
}
protected Object doBatchLoad(
Serializable id,
Loader loaderToUse,
SessionImplementor session,
Serializable[] ids,
Object optionalObject,
LockOptions lockOptions) {
if ( log.isDebugEnabled() ) {
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, session.getFactory() ) );
}
QueryParameters qp = buildQueryParameters( id, ids, optionalObject, lockOptions );
try {
final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false );
log.debug( "Done entity batch load" );
return getObjectFromList(results, id, session);
}
catch ( SQLException sqle ) {
throw session.getFactory().getSQLExceptionHelper().convert(
sqle,
"could not load an entity batch: " + MessageHelper.infoString( persister(), ids, session.getFactory() ),
loaderToUse.getSQLString()
);
}
}
}

View File

@ -0,0 +1,179 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.loader.entity.plan;
import org.jboss.logging.Logger;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.type.Type;
/**
* Loads an entity instance using outerjoin fetching to fetch associated entities.
* <br>
* The <tt>EntityPersister</tt> must implement <tt>Loadable</tt>. For other entities,
* create a customized subclass of <tt>Loader</tt>.
*
* @author Gavin King
*/
public class EntityLoader extends AbstractLoadPlanBasedEntityLoader {
private static final Logger log = CoreLogging.logger( EntityLoader.class );
// private final boolean batchLoader;
// private final int[][] compositeKeyManyToOneTargetIndices;
//
// public EntityLoader(
// OuterJoinLoadable persister,
// LockMode lockMode,
// SessionFactoryImplementor factory,
// LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
// this( persister, 1, lockMode, factory, loadQueryInfluencers );
// }
//
// public EntityLoader(
// OuterJoinLoadable persister,
// LockOptions lockOptions,
// SessionFactoryImplementor factory,
// LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
// this( persister, 1, lockOptions, factory, loadQueryInfluencers );
// }
public EntityLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
this(
persister,
persister.getIdentifierColumnNames(),
persister.getIdentifierType(),
batchSize,
lockMode,
factory,
loadQueryInfluencers
);
}
public EntityLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
this(
persister,
persister.getIdentifierColumnNames(),
persister.getIdentifierType(),
batchSize,
lockOptions,
factory,
loadQueryInfluencers
);
}
public EntityLoader(
OuterJoinLoadable persister,
String[] uniqueKey,
Type uniqueKeyType,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
super( persister, uniqueKeyType, factory, loadQueryInfluencers );
// EntityJoinWalker walker = new EntityJoinWalker(
// persister,
// uniqueKey,
// batchSize,
// lockMode,
// factory,
// loadQueryInfluencers
// );
// initFromWalker( walker );
// this.compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices();
// postInstantiate();
//
// batchLoader = batchSize > 1;
//
if ( log.isDebugEnabled() ) {
log.debugf( "Static select for entity %s [%s]: %s", getEntityName(), lockMode, getSqlStatement() );
}
}
public EntityLoader(
OuterJoinLoadable persister,
String[] uniqueKey,
Type uniqueKeyType,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
super( persister, uniqueKeyType, factory, loadQueryInfluencers );
//
// EntityJoinWalker walker = new EntityJoinWalker(
// persister,
// uniqueKey,
// batchSize,
// lockOptions,
// factory,
// loadQueryInfluencers
// );
// initFromWalker( walker );
// this.compositeKeyManyToOneTargetIndices = walker.getCompositeKeyManyToOneTargetIndices();
// postInstantiate();
//
// batchLoader = batchSize > 1;
//
if ( log.isDebugEnabled() ) {
log.debugf(
"Static select for entity %s [%s:%s]: %s",
getEntityName(),
lockOptions.getLockMode(),
lockOptions.getTimeOut(),
getSqlStatement()
);
}
}
// public Object loadByUniqueKey(SessionImplementor session,Object key) {
// return load( session, key, null, null, LockOptions.NONE );
// }
//
// @Override
// protected boolean isSingleRowLoader() {
// return !batchLoader;
// }
//
// @Override
// public int[][] getCompositeKeyManyToOneTargetIndices() {
// return compositeKeyManyToOneTargetIndices;
// }
}

View File

@ -0,0 +1,129 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.entity.plan;
import java.io.Serializable;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.loader.entity.BatchingEntityLoader;
import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
import org.hibernate.loader.entity.UniqueEntityLoader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
public static final LegacyBatchingEntityLoaderBuilder INSTANCE = new LegacyBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new LegacyBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new LegacyBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
public static class LegacyBatchingEntityLoader extends BatchingEntityLoader implements UniqueEntityLoader {
private final int[] batchSizes;
private final EntityLoader[] loaders;
public LegacyBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new EntityLoader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers);
}
}
public LegacyBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new EntityLoader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers);
}
}
@Override
public Object load(Serializable id, Object optionalObject, SessionImplementor session, LockOptions lockOptions) {
final Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
for ( int i = 0; i < batchSizes.length-1; i++) {
final int smallBatchSize = batchSizes[i];
if ( batch[smallBatchSize-1] != null ) {
Serializable[] smallBatch = new Serializable[smallBatchSize];
System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize);
// for now...
final List results = loaders[i].loadEntityBatch(
session,
smallBatch,
persister().getIdentifierType(),
optionalObject,
persister().getEntityName(),
id,
persister(),
lockOptions
);
//EARLY EXIT
return getObjectFromList( results, id, session );
}
}
return ( loaders[batchSizes.length-1] ).load( id, optionalObject, session );
}
}
}