diff --git a/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java b/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java index 95d31f5145..7d6f8200ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java +++ b/hibernate-core/src/main/java/org/hibernate/PropertyAccessException.java @@ -71,9 +71,13 @@ public class PropertyAccessException extends HibernateException { return propertyName; } + protected String originalMessage() { + return super.getMessage(); + } + @Override public String getMessage() { - return super.getMessage() + return originalMessage() + ( wasSetter ? " setter of " : " getter of " ) + StringHelper.qualify( persistentClass.getName(), propertyName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/PropertySetterAccessException.java b/hibernate-core/src/main/java/org/hibernate/PropertySetterAccessException.java new file mode 100644 index 0000000000..d84a4e68f8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/PropertySetterAccessException.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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; + +/** + * @author Steve Ebersole + */ +public class PropertySetterAccessException extends PropertyAccessException { + /** + * Constructs a PropertyAccessException using the specified information. + * + * @param cause The underlying cause + * @param persistentClass The class which is supposed to contain the property in question + * @param propertyName The name of the property. + * @param expectedType The expected property type + * @param target The target, which should be of type 'persistentClass' + * @param value The property value we are trying to set + */ + public PropertySetterAccessException( + Throwable cause, + Class persistentClass, + String propertyName, + Class expectedType, + Object target, + Object value) { + super( + cause, + String.format( + "IllegalArgumentException occurred while calling setter for property [%s.%s (expected type = %s)]; " + + "target = [%s], property value = [%s]", + persistentClass.getName(), + propertyName, + expectedType.getName(), + target, + value + ), + true, + persistentClass, + propertyName + ); + } + + @Override + public String toString() { + return super.originalMessage(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Any.java b/hibernate-core/src/main/java/org/hibernate/annotations/Any.java index 7ec5a179f5..786695cc9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Any.java @@ -57,6 +57,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * * @author Emmanuel Bernard * @author Steve Ebersole + * + * @see AnyMetaDef */ @java.lang.annotation.Target({METHOD, FIELD}) @Retention(RUNTIME) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index aee333ad27..899e98a32d 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -28,6 +28,7 @@ import java.util.Properties; import java.util.Set; import org.hibernate.CustomEntityDirtinessStrategy; +import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MappingException; @@ -290,4 +291,6 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory { * @return */ public NamedQueryRepository getNamedQueryRepository(); + + Iterable iterateEntityNameResolvers(); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/NameGenerator.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/NameGenerator.java index 434f432fdf..adad67ae05 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/NameGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/NameGenerator.java @@ -52,12 +52,22 @@ public final class NameGenerator { } public static String scalarName(int x, int y) { - return new StringBuilder() - .append( "col_" ) - .append( x ) - .append( '_' ) - .append( y ) - .append( '_' ) - .toString(); + return scalarName( "col_" + x, y ); + } + + public static String scalarName(String base, int num) { + return base + '_' + num + '_'; + } + + public static String[] scalarNames(String base, int count) { + final String[] names = new String[count]; + for ( int j = 0; j < count; j++ ) { + names[j] = scalarName( base, j ); + } + return names; + } + + public static String[] scalarNames(int uniqueness, int count) { + return scalarNames( "col_" + uniqueness, count ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java index 81003e2284..a37f769b7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/HqlSqlWalker.java @@ -378,6 +378,11 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par final FromElement fromElement; if ( dot.getDataType() != null && dot.getDataType().isComponentType() ) { + if ( dot.getDataType().isAnyType() ) { + throw new SemanticException( "An AnyType attribute cannot be join fetched" ); + // ^^ because the discriminator (aka, the "meta columns") must be known to the SQL in + // a non-parameterized way. + } FromElementFactory factory = new FromElementFactory( getCurrentFromClause(), dot.getLhs().getFromElement(), diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 2f52e18f3b..a5d70ad49e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -35,7 +35,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -268,7 +267,6 @@ public final class SessionFactoryImpl this.jdbcServices = this.serviceRegistry.getService( JdbcServices.class ); this.dialect = this.jdbcServices.getDialect(); this.cacheAccess = this.serviceRegistry.getService( CacheImplementor.class ); - final RegionFactory regionFactory = cacheAccess.getRegionFactory(); this.sqlFunctionRegistry = new SQLFunctionRegistry( getDialect(), cfg.getSqlFunctions() ); if ( observer != null ) { this.observer.addObserver( observer ); @@ -329,15 +327,22 @@ public final class SessionFactoryImpl } } + imports = new HashMap( cfg.getImports() ); /////////////////////////////////////////////////////////////////////// // Prepare persisters and link them up with their cache // region/access-strategy + final RegionFactory regionFactory = cacheAccess.getRegionFactory(); final String cacheRegionPrefix = settings.getCacheRegionPrefix() == null ? "" : settings.getCacheRegionPrefix() + "."; - final PersisterFactory persisterFactory = serviceRegistry.getService( PersisterFactory.class ); + // todo : consider removing this silliness and just have EntityPersister directly implement ClassMetadata + // EntityPersister.getClassMetadata() for the internal impls simply "return this"; + // collapsing those would allow us to remove this "extra" Map + // + // todo : similar for CollectionPersister/CollectionMetadata + entityPersisters = new HashMap(); Map entityAccessStrategies = new HashMap(); Map classMeta = new HashMap(); @@ -358,15 +363,15 @@ public final class SessionFactoryImpl cacheAccess.addCacheRegion( cacheRegionName, entityRegion ); } } - + NaturalIdRegionAccessStrategy naturalIdAccessStrategy = null; if ( model.hasNaturalId() && model.getNaturalIdCacheRegionName() != null ) { final String naturalIdCacheRegionName = cacheRegionPrefix + model.getNaturalIdCacheRegionName(); naturalIdAccessStrategy = ( NaturalIdRegionAccessStrategy ) entityAccessStrategies.get( naturalIdCacheRegionName ); - + if ( naturalIdAccessStrategy == null && settings.isSecondLevelCacheEnabled() ) { final CacheDataDescriptionImpl cacheDataDescription = CacheDataDescriptionImpl.decode( model ); - + NaturalIdRegion naturalIdRegion = null; try { naturalIdRegion = regionFactory.buildNaturalIdRegion( naturalIdCacheRegionName, properties, @@ -380,7 +385,7 @@ public final class SessionFactoryImpl model.getEntityName() ); } - + if (naturalIdRegion != null) { naturalIdAccessStrategy = naturalIdRegion.buildAccessStrategy( regionFactory.getDefaultAccessType() ); entityAccessStrategies.put( naturalIdCacheRegionName, naturalIdAccessStrategy ); @@ -388,7 +393,7 @@ public final class SessionFactoryImpl } } } - + EntityPersister cp = persisterFactory.createEntityPersister( model, accessStrategy, @@ -462,19 +467,13 @@ public final class SessionFactoryImpl cfg.getSqlResultSetMappings().values(), toProcedureCallMementos( cfg.getNamedProcedureCallMap(), cfg.getSqlResultSetMappings() ) ); - imports = new HashMap( cfg.getImports() ); // after *all* persisters and named queries are registered - Iterator iter = entityPersisters.values().iterator(); - while ( iter.hasNext() ) { - final EntityPersister persister = ( ( EntityPersister ) iter.next() ); + for ( EntityPersister persister : entityPersisters.values() ) { persister.postInstantiate(); registerEntityNameResolvers( persister ); - } - iter = collectionPersisters.values().iterator(); - while ( iter.hasNext() ) { - final CollectionPersister persister = ( ( CollectionPersister ) iter.next() ); + for ( CollectionPersister persister : collectionPersisters.values() ) { persister.postInstantiate(); } @@ -1070,6 +1069,7 @@ public final class SessionFactoryImpl entityNameResolvers.put( resolver, ENTITY_NAME_RESOLVER_MAP_VALUE ); } + @Override public Iterable iterateEntityNameResolvers() { return entityNameResolvers.keySet(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/GeneratedCollectionAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/GeneratedCollectionAliases.java index 0780587831..dafb0837c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/GeneratedCollectionAliases.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/GeneratedCollectionAliases.java @@ -51,20 +51,22 @@ public class GeneratedCollectionAliases implements CollectionAliases { this.keyAliases = getUserProvidedAliases( "key", persister.getKeyColumnAliases( suffix ) - ); + ); this.indexAliases = getUserProvidedAliases( "index", persister.getIndexColumnAliases( suffix ) - ); + ); - this.elementAliases = getUserProvidedAliases( "element", + this.elementAliases = getUserProvidedAliases( + "element", persister.getElementColumnAliases( suffix ) - ); + ); - this.identifierAlias = getUserProvidedAlias( "id", + this.identifierAlias = getUserProvidedAlias( + "id", persister.getIdentifierColumnAlias( suffix ) - ); + ); } public GeneratedCollectionAliases(CollectionPersister persister, String string) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java b/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java index affb387682..c415c6e7a4 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/JoinWalker.java @@ -24,6 +24,7 @@ package org.hibernate.loader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -91,6 +92,9 @@ public class JoinWalker { } + public List getAssociations() { + return Collections.unmodifiableList( associations ); + } public String[] getCollectionSuffixes() { return collectionSuffixes; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java index e2a9c247f1..116716903a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoaderBuilder.java @@ -72,11 +72,19 @@ public abstract class BatchingEntityLoaderBuilder { LoadQueryInfluencers influencers) { if ( batchSize <= 1 ) { // no batching - return new EntityLoader( persister, lockMode, factory, influencers ); + return buildNonBatchingLoader( persister, lockMode, factory, influencers ); } return buildBatchingLoader( persister, batchSize, lockMode, factory, influencers ); } + protected UniqueEntityLoader buildNonBatchingLoader( + OuterJoinLoadable persister, + LockMode lockMode, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return new EntityLoader( persister, lockMode, factory, influencers ); + } + protected abstract UniqueEntityLoader buildBatchingLoader( OuterJoinLoadable persister, int batchSize, @@ -103,11 +111,19 @@ public abstract class BatchingEntityLoaderBuilder { LoadQueryInfluencers influencers) { if ( batchSize <= 1 ) { // no batching - return new EntityLoader( persister, lockOptions, factory, influencers ); + return buildNonBatchingLoader( persister, lockOptions, factory, influencers ); } return buildBatchingLoader( persister, batchSize, lockOptions, factory, influencers ); } + protected UniqueEntityLoader buildNonBatchingLoader( + OuterJoinLoadable persister, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return new EntityLoader( persister, lockOptions, factory, influencers ); + } + protected abstract UniqueEntityLoader buildBatchingLoader( OuterJoinLoadable persister, int batchSize, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractBatchingEntityLoaderBuilder.java new file mode 100644 index 0000000000..657743d6d5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractBatchingEntityLoaderBuilder.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.entity.BatchingEntityLoaderBuilder; +import org.hibernate.loader.entity.UniqueEntityLoader; +import org.hibernate.persister.entity.OuterJoinLoadable; + +/** + * Base class for LoadPlan-based BatchingEntityLoaderBuilder implementations. Mainly we handle the common + * "no batching" case here to use the LoadPlan-based EntityLoader + * + * @author Steve Ebersole + */ +public abstract class AbstractBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder { + @Override + protected UniqueEntityLoader buildNonBatchingLoader( + OuterJoinLoadable persister, + LockMode lockMode, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return EntityLoader.forEntity( persister ).withLockMode( lockMode ).withInfluencers( influencers ).byPrimaryKey(); + } + + @Override + protected UniqueEntityLoader buildNonBatchingLoader( + OuterJoinLoadable persister, + LockOptions lockOptions, + SessionFactoryImplementor factory, + LoadQueryInfluencers influencers) { + return EntityLoader.forEntity( persister ).withLockOptions( lockOptions ).withInfluencers( influencers ).byPrimaryKey(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 48661de5d5..9f66629638 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -8,7 +8,6 @@ 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; @@ -22,7 +21,6 @@ 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; @@ -32,17 +30,14 @@ 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.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; +import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; 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; @@ -63,63 +58,41 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL private final String entityName; private final LoadPlan plan; - private final String staticSql; - private final LoadQueryAliasResolutionContext staticAliasResolutionContext; - private final ResultSetProcessor staticResultSetProcessor; + private final LoadQueryDetails staticLoadQuery; private ColumnNameCache columnNameCache; public AbstractLoadPlanBasedEntityLoader( OuterJoinLoadable entityPersister, - Type uniqueKeyType, SessionFactoryImplementor factory, - LoadQueryInfluencers loadQueryInfluencers) { + String[] uniqueKeyColumnNames, + Type uniqueKeyType, + QueryBuildingParameters buildingParameters) { + this.entityPersister = entityPersister; this.factory = factory; this.uniqueKeyType = uniqueKeyType; this.entityName = entityPersister.getEntityName(); - this.entityPersister = entityPersister; final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( factory, - loadQueryInfluencers + buildingParameters.getQueryInfluencers() ); 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( + this.staticLoadQuery = LoadQueryDetails.makeForBatching( + uniqueKeyColumnNames, + plan, factory, - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] {"abc"} ) + buildingParameters ); } - 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 LoadQueryDetails getStaticLoadQuery() { + return staticLoadQuery; } protected String getEntityName() { @@ -152,17 +125,12 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL 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, + staticLoadQuery, false, null ); @@ -171,7 +139,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL throw factory.getSQLExceptionHelper().convert( sqle, "could not load an entity batch: " + MessageHelper.infoString( entityPersister, ids, getFactory() ), - getSqlStatement() + staticLoadQuery.getSqlStatement() ); } @@ -203,9 +171,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL final List results = executeLoad( session, qp, - staticSql, - staticResultSetProcessor, - staticAliasResolutionContext, + staticLoadQuery, false, null ); @@ -220,7 +186,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL entityPersister.getIdentifierType(), factory ), - getSqlStatement() + staticLoadQuery.getSqlStatement() ); } @@ -231,18 +197,14 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL protected List executeLoad( SessionImplementor session, QueryParameters queryParameters, - String sql, - ResultSetProcessor resultSetProcessor, - LoadQueryAliasResolutionContext aliasResolutionContext, + LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer) throws SQLException { final List afterLoadActions = new ArrayList(); return executeLoad( session, queryParameters, - sql, - resultSetProcessor, - aliasResolutionContext, + loadQueryDetails, returnProxies, forcedResultTransformer, afterLoadActions @@ -252,9 +214,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL protected List executeLoad( SessionImplementor session, QueryParameters queryParameters, - String sql, - ResultSetProcessor resultSetProcessor, - LoadQueryAliasResolutionContext aliasResolutionContext, + LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer, List afterLoadActions) throws SQLException { @@ -273,9 +233,10 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL persistenceContext.beforeLoad(); try { List results; + final String sql = loadQueryDetails.getSqlStatement(); try { final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); - results = resultSetProcessor.extractResults( + results = loadQueryDetails.getResultSetProcessor().extractResults( // todo : hook in the JPA 2.1 entity graph advisor NoOpLoadPlanAdvisor.INSTANCE, wrapper.getResultSet(), @@ -287,7 +248,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL return AbstractLoadPlanBasedEntityLoader.this.getNamedParameterLocs( name ); } }, - aliasResolutionContext, + loadQueryDetails.getAliasResolutionContext(), returnProxies, queryParameters.isReadOnly(), forcedResultTransformer, @@ -334,9 +295,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL protected Object doQueryAndLoadEntity( SessionImplementor session, QueryParameters queryParameters, - String sql, - ResultSetProcessor resultSetProcessor, - LoadQueryAliasResolutionContext aliasResolutionContext, + LoadQueryDetails loadQueryDetails, boolean returnProxies, ResultTransformer forcedResultTransformer) throws SQLException { @@ -345,7 +304,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, false, afterLoadActions, session ); try { - final List results = resultSetProcessor.extractResults( + final List results = loadQueryDetails.getResultSetProcessor().extractResults( NoOpLoadPlanAdvisor.INSTANCE, wrapper.getResultSet(), session, @@ -356,7 +315,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL return AbstractLoadPlanBasedEntityLoader.this.getNamedParameterLocs( name ); } }, - staticAliasResolutionContext, + loadQueryDetails.getAliasResolutionContext(), returnProxies, queryParameters.isReadOnly(), forcedResultTransformer, @@ -398,7 +357,7 @@ public abstract class AbstractLoadPlanBasedEntityLoader implements UniqueEntityL final boolean scroll, List afterLoadActions, final SessionImplementor session) throws SQLException { - return executeQueryStatement( getSqlStatement(), queryParameters, scroll, afterLoadActions, session ); + return executeQueryStatement( staticLoadQuery.getSqlStatement(), queryParameters, scroll, afterLoadActions, session ); } protected SqlStatementWrapper executeQueryStatement( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java index 5bf03ec81a..b8e9d5f820 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java @@ -40,8 +40,10 @@ 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 WHERE clause. + * The base contract for UniqueEntityLoader implementations capable of performing batch-fetch loading of entities + * using multiple primary key values in the SQL WHERE clause. + *

+ * Typically these are * * @author Gavin King * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java index 14068c13e6..4b7a3f3bfc 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/EntityLoader.java @@ -32,148 +32,125 @@ import org.hibernate.MappingException; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.type.Type; /** + * UniqueEntityLoader implementation that is the main functionality for LoadPlan-based Entity loading. + *

+ * Can handle batch-loading as well as non-pk, unique-key loading, + *

+ * Much is ultimately delegated to its superclass, AbstractLoadPlanBasedEntityLoader. However: + * todo How much of AbstractLoadPlanBasedEntityLoader is actually needed? + * * Loads an entity instance using outerjoin fetching to fetch associated entities. *
* The EntityPersister must implement Loadable. For other entities, * create a customized subclass of Loader. * * @author Gavin King + * @author Steve Ebersole + * @author Gail Badner */ 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 static Builder forEntity(OuterJoinLoadable persister) { + return new Builder( persister ); } - 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 static class Builder { + private final OuterJoinLoadable persister; + private int batchSize = 1; + private LoadQueryInfluencers influencers = LoadQueryInfluencers.NONE; + private LockMode lockMode = LockMode.NONE; + private LockOptions lockOptions; - 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 Builder(OuterJoinLoadable persister) { + this.persister = persister; } - } - 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 Builder withBatchSize(int batchSize) { + this.batchSize = batchSize; + return this; + } + + public Builder withInfluencers(LoadQueryInfluencers influencers) { + this.influencers = influencers; + return this; + } + + public Builder withLockMode(LockMode lockMode) { + this.lockMode = lockMode; + return this; + } + + public Builder withLockOptions(LockOptions lockOptions) { + this.lockOptions = lockOptions; + return this; + } + + public EntityLoader byPrimaryKey() { + return byUniqueKey( persister.getIdentifierColumnNames(), persister.getIdentifierType() ); + } + + public EntityLoader byUniqueKey(String[] keyColumnNames, Type keyType) { + return new EntityLoader( + persister.getFactory(), + persister, + keyColumnNames, + keyType, + new QueryBuildingParameters() { + @Override + public LoadQueryInfluencers getQueryInfluencers() { + return influencers; + } + + @Override + public int getBatchSize() { + return batchSize; + } + + @Override + public LockMode getLockMode() { + return lockMode; + } + + @Override + public LockOptions getLockOptions() { + return lockOptions; + } + } ); } } -// 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; -// } + private EntityLoader( + SessionFactoryImplementor factory, + OuterJoinLoadable persister, + String[] uniqueKeyColumnNames, + Type uniqueKeyType, + QueryBuildingParameters buildingParameters) throws MappingException { + super( persister, factory, uniqueKeyColumnNames, uniqueKeyType, buildingParameters ); + if ( log.isDebugEnabled() ) { + if ( buildingParameters.getLockOptions() != null ) { + log.debugf( + "Static select for entity %s [%s:%s]: %s", + getEntityName(), + buildingParameters.getLockOptions().getLockMode(), + buildingParameters.getLockOptions().getTimeOut(), + getStaticLoadQuery().getSqlStatement() + ); + } + else if ( buildingParameters.getLockMode() != null ) { + log.debugf( + "Static select for entity %s [%s]: %s", + getEntityName(), + buildingParameters.getLockMode(), + getStaticLoadQuery().getSqlStatement() + ); + } + } + } } \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java index d3239f042b..474364e5f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java @@ -32,16 +32,15 @@ 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; /** + * LoadPlan-based implementation of the the legacy batch loading strategy + * * @author Steve Ebersole */ -public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder { +public class LegacyBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder { public static final LegacyBatchingEntityLoaderBuilder INSTANCE = new LegacyBatchingEntityLoaderBuilder(); @Override @@ -77,8 +76,11 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild super( persister ); this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); this.loaders = new EntityLoader[ batchSizes.length ]; + final EntityLoader.Builder entityLoaderBuilder = EntityLoader.forEntity( persister ) + .withInfluencers( loadQueryInfluencers ) + .withLockMode( lockMode ); for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers); + this.loaders[i] = entityLoaderBuilder.withBatchSize( batchSizes[i] ).byPrimaryKey(); } } @@ -91,8 +93,11 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild super( persister ); this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize ); this.loaders = new EntityLoader[ batchSizes.length ]; + final EntityLoader.Builder entityLoaderBuilder = EntityLoader.forEntity( persister ) + .withInfluencers( loadQueryInfluencers ) + .withLockOptions( lockOptions ); for ( int i = 0; i < batchSizes.length; i++ ) { - this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers); + this.loaders[i] = entityLoaderBuilder.withBatchSize( batchSizes[i] ).byPrimaryKey(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractEntityLoadQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractEntityLoadQueryImpl.java deleted file mode 100755 index e9745d860f..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractEntityLoadQueryImpl.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.internal; -import java.util.List; - -import org.hibernate.LockOptions; -import org.hibernate.MappingException; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.plan.spi.EntityReturn; -import org.hibernate.loader.spi.JoinableAssociation; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.persister.entity.OuterJoinLoadable; -import org.hibernate.sql.JoinFragment; -import org.hibernate.sql.Select; - -/** - * Represents an entity load query for criteria - * queries and entity loaders, used for generating SQL. - * - * This code is based on the SQL generation code originally in - * org.hibernate.loader.AbstractEntityJoinWalker. - * - * @author Gavin King - * @author Gail Badner - */ -public abstract class AbstractEntityLoadQueryImpl extends AbstractLoadQueryImpl { - - private final EntityReturn entityReturn; - - public AbstractEntityLoadQueryImpl(EntityReturn entityReturn, List associations) { - super( associations ); - this.entityReturn = entityReturn; - } - - protected final String generateSql( - final String whereString, - final String orderByString, - final LockOptions lockOptions, - final SessionFactoryImplementor factory, - final LoadQueryAliasResolutionContext aliasResolutionContext) throws MappingException { - return generateSql( null, whereString, orderByString, "", lockOptions, factory, aliasResolutionContext ); - } - - private String generateSql( - final String projection, - final String condition, - final String orderBy, - final String groupBy, - final LockOptions lockOptions, - final SessionFactoryImplementor factory, - final LoadQueryAliasResolutionContext aliasResolutionContext) throws MappingException { - - JoinFragment ojf = mergeOuterJoins( factory, aliasResolutionContext ); - - // If no projection, then the last suffix should be for the entity return. - // TODO: simplify how suffixes are generated/processed. - - final String entityReturnAlias = resolveEntityReturnAlias( aliasResolutionContext ); - Select select = new Select( factory.getDialect() ) - .setLockOptions( lockOptions ) - .setSelectClause( - projection == null ? - getPersister().selectFragment( - entityReturnAlias, - aliasResolutionContext.resolveEntityColumnAliases( entityReturn ).getSuffix() - ) + associationSelectString( aliasResolutionContext ) : - projection - ) - .setFromClause( - factory.getDialect().appendLockHint( - lockOptions, - getPersister().fromTableFragment( entityReturnAlias ) - ) + getPersister().fromJoinFragment( entityReturnAlias, true, true ) - ) - .setWhereClause( condition ) - .setOuterJoins( - ojf.toFromFragmentString(), - ojf.toWhereFragmentString() + getWhereFragment( aliasResolutionContext ) - ) - .setOrderByClause( orderBy( orderBy, aliasResolutionContext ) ) - .setGroupByClause( groupBy ); - - if ( factory.getSettings().isCommentsEnabled() ) { - select.setComment( getComment() ); - } - return select.toStatementString(); - } - - protected String getWhereFragment(LoadQueryAliasResolutionContext aliasResolutionContext) throws MappingException { - // here we do not bother with the discriminator. - return getPersister().whereJoinFragment( resolveEntityReturnAlias( aliasResolutionContext ), true, true ); - } - - protected abstract String getComment(); - - protected final OuterJoinLoadable getPersister() { - return (OuterJoinLoadable) entityReturn.getEntityPersister(); - } - - protected final String resolveEntityReturnAlias(LoadQueryAliasResolutionContext aliasResolutionContext) { - return aliasResolutionContext.resolveEntityTableAlias( entityReturn ); - } - - public String toString() { - return getClass().getName() + '(' + getPersister().getEntityName() + ')'; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractLoadQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractLoadQueryImpl.java index 15ba337255..9e75c62e9e 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractLoadQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/AbstractLoadQueryImpl.java @@ -30,10 +30,12 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.CollectionAliases; import org.hibernate.loader.EntityAliases; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.spi.Fetch; import org.hibernate.loader.spi.JoinableAssociation; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.Joinable; +import org.hibernate.persister.walking.internal.FetchStrategyHelper; import org.hibernate.sql.ConditionFragment; import org.hibernate.sql.DisjunctionFragment; import org.hibernate.sql.InFragment; @@ -58,7 +60,7 @@ public abstract class AbstractLoadQueryImpl { this.associations = associations; } - protected String orderBy(final String orderBy, LoadQueryAliasResolutionContext aliasResolutionContext) { + protected String orderBy(final String orderBy, AliasResolutionContext aliasResolutionContext) { return mergeOrderings( orderBy( associations, aliasResolutionContext ), orderBy ); } @@ -77,7 +79,7 @@ public abstract class AbstractLoadQueryImpl { /** * Generate a sequence of LEFT OUTER JOIN clauses for the given associations. */ - protected final JoinFragment mergeOuterJoins(SessionFactoryImplementor factory, LoadQueryAliasResolutionContext aliasResolutionContext) + protected final JoinFragment mergeOuterJoins(SessionFactoryImplementor factory, AliasResolutionContext aliasResolutionContext) throws MappingException { JoinFragment joinFragment = factory.getDialect().createOuterJoinFragment(); JoinableAssociation previous = null; @@ -120,7 +122,7 @@ public abstract class AbstractLoadQueryImpl { // TODO: why is this static? protected static String orderBy( List associations, - LoadQueryAliasResolutionContext aliasResolutionContext) + AliasResolutionContext aliasResolutionContext) throws MappingException { StringBuilder buf = new StringBuilder(); JoinableAssociation previous = null; @@ -197,7 +199,7 @@ public abstract class AbstractLoadQueryImpl { /** * Generate a select list of columns containing all properties of the entity classes */ - protected final String associationSelectString(LoadQueryAliasResolutionContext aliasResolutionContext) + protected final String associationSelectString(AliasResolutionContext aliasResolutionContext) throws MappingException { if ( associations.size() == 0 ) { @@ -210,15 +212,19 @@ public abstract class AbstractLoadQueryImpl { JoinableAssociation next = ( i == associations.size() - 1 ) ? null : associations.get( i + 1 ); + if ( !shouldAddToSql( association.getCurrentFetch() ) ) { + continue; + } + final Joinable joinable = association.getJoinable(); final EntityAliases currentEntityAliases = association.getCurrentEntityReference() == null ? null : - aliasResolutionContext.resolveEntityColumnAliases( association.getCurrentEntityReference() ); + aliasResolutionContext.resolveAliases( association.getCurrentEntityReference() ).getColumnAliases(); final CollectionAliases currentCollectionAliases = association.getCurrentCollectionReference() == null ? null : - aliasResolutionContext.resolveCollectionColumnAliases( association.getCurrentCollectionReference() ); + aliasResolutionContext.resolveAliases( association.getCurrentCollectionReference() ).getCollectionColumnAliases(); final String selectFragment = joinable.selectFragment( next == null ? null : next.getJoinable(), next == null ? null : aliasResolutionContext.resolveAssociationRhsTableAlias( next ), @@ -236,6 +242,10 @@ public abstract class AbstractLoadQueryImpl { } } + private boolean shouldAddToSql(Fetch fetch) { + return FetchStrategyHelper.isJoinFetched( fetch.getFetchStrategy() ); + } + private void addJoins( JoinFragment joinFragment, JoinableAssociation association, @@ -260,7 +270,7 @@ public abstract class AbstractLoadQueryImpl { private String resolveOnCondition( SessionFactoryImplementor factory, JoinableAssociation joinableAssociation, - LoadQueryAliasResolutionContext aliasResolutionContext) { + AliasResolutionContext aliasResolutionContext) { final String withClause = StringHelper.isEmpty( joinableAssociation.getWithClause() ) ? "" : " and ( " + joinableAssociation.getWithClause() + " )"; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java index 1f6b5aaa32..02b1c9cba4 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityJoinableAssociationImpl.java @@ -58,7 +58,7 @@ public class EntityJoinableAssociationImpl extends AbstractJoinableAssociationIm hasRestriction, enabledFilters ); - this.joinableType = entityFetch.getEntityType(); + this.joinableType = entityFetch.getFetchedType(); this.joinable = (Joinable) entityFetch.getEntityPersister(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java deleted file mode 100644 index aef90267f1..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryBuilderImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.internal; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; - -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.plan.spi.CollectionFetch; -import org.hibernate.loader.plan.spi.CollectionReference; -import org.hibernate.loader.plan.spi.CompositeFetch; -import org.hibernate.loader.plan.spi.EntityFetch; -import org.hibernate.loader.plan.spi.EntityReference; -import org.hibernate.loader.plan.spi.EntityReturn; -import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.visit.LoadPlanVisitationStrategyAdapter; -import org.hibernate.loader.plan.spi.visit.LoadPlanVisitor; -import org.hibernate.loader.plan.spi.Return; -import org.hibernate.loader.spi.JoinableAssociation; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.LoadQueryBuilder; -import org.hibernate.persister.entity.OuterJoinLoadable; -import org.hibernate.persister.walking.spi.WalkingException; - -/** - * @author Gail Badner - */ -public class EntityLoadQueryBuilderImpl implements LoadQueryBuilder { - private final LoadQueryInfluencers loadQueryInfluencers; - private final LoadPlan loadPlan; - private final List associations; - - public EntityLoadQueryBuilderImpl( - LoadQueryInfluencers loadQueryInfluencers, - LoadPlan loadPlan) { - this.loadQueryInfluencers = loadQueryInfluencers; - this.loadPlan = loadPlan; - - // TODO: the whole point of the following is to build associations. - // this could be done while building loadPlan (and be a part of the LoadPlan). - // Should it be? - LocalVisitationStrategy strategy = new LocalVisitationStrategy(); - LoadPlanVisitor.visit( loadPlan, strategy ); - this.associations = strategy.associations; - } - - @Override - public String generateSql( - int batchSize, - SessionFactoryImplementor sessionFactory, - LoadQueryAliasResolutionContext aliasResolutionContext) { - return generateSql( - batchSize, - getOuterJoinLoadable().getKeyColumnNames(), - sessionFactory, - aliasResolutionContext - ); - } - - public String generateSql( - int batchSize, - String[] uniqueKey, - SessionFactoryImplementor sessionFactory, - LoadQueryAliasResolutionContext aliasResolutionContext) { - final EntityLoadQueryImpl loadQuery = new EntityLoadQueryImpl( - getRootEntityReturn(), - associations - ); - return loadQuery.generateSql( - uniqueKey, - batchSize, - getRootEntityReturn().getLockMode(), - sessionFactory, - aliasResolutionContext ); - } - - private EntityReturn getRootEntityReturn() { - return (EntityReturn) loadPlan.getReturns().get( 0 ); - } - - private OuterJoinLoadable getOuterJoinLoadable() { - return (OuterJoinLoadable) getRootEntityReturn().getEntityPersister(); - } - - private class LocalVisitationStrategy extends LoadPlanVisitationStrategyAdapter { - private final List associations = new ArrayList(); - private Deque entityReferenceStack = new ArrayDeque(); - private Deque collectionReferenceStack = new ArrayDeque(); - - private EntityReturn entityRootReturn; - - @Override - public void handleEntityReturn(EntityReturn rootEntityReturn) { - this.entityRootReturn = rootEntityReturn; - } - - @Override - public void startingRootReturn(Return rootReturn) { - if ( !EntityReturn.class.isInstance( rootReturn ) ) { - throw new WalkingException( - String.format( - "Unexpected type of return; expected [%s]; instead it was [%s]", - EntityReturn.class.getName(), - rootReturn.getClass().getName() - ) - ); - } - this.entityRootReturn = (EntityReturn) rootReturn; - pushToStack( entityReferenceStack, entityRootReturn ); - } - - @Override - public void finishingRootReturn(Return rootReturn) { - if ( !EntityReturn.class.isInstance( rootReturn ) ) { - throw new WalkingException( - String.format( - "Unexpected type of return; expected [%s]; instead it was [%s]", - EntityReturn.class.getName(), - rootReturn.getClass().getName() - ) - ); - } - popFromStack( entityReferenceStack, entityRootReturn ); - } - - @Override - public void startingEntityFetch(EntityFetch entityFetch) { - EntityJoinableAssociationImpl assoc = new EntityJoinableAssociationImpl( - entityFetch, - getCurrentCollectionReference(), - "", // getWithClause( entityFetch.getPropertyPath() ) - false, // hasRestriction( entityFetch.getPropertyPath() ) - loadQueryInfluencers.getEnabledFilters() - ); - associations.add( assoc ); - pushToStack( entityReferenceStack, entityFetch ); - } - - @Override - public void finishingEntityFetch(EntityFetch entityFetch) { - popFromStack( entityReferenceStack, entityFetch ); - } - - @Override - public void startingCollectionFetch(CollectionFetch collectionFetch) { - CollectionJoinableAssociationImpl assoc = new CollectionJoinableAssociationImpl( - collectionFetch, - getCurrentEntityReference(), - "", // getWithClause( entityFetch.getPropertyPath() ) - false, // hasRestriction( entityFetch.getPropertyPath() ) - loadQueryInfluencers.getEnabledFilters() - ); - associations.add( assoc ); - pushToStack( collectionReferenceStack, collectionFetch ); - } - - @Override - public void finishingCollectionFetch(CollectionFetch collectionFetch) { - popFromStack( collectionReferenceStack, collectionFetch ); - } - - @Override - public void startingCompositeFetch(CompositeFetch fetch) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void finishingCompositeFetch(CompositeFetch fetch) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void finish(LoadPlan loadPlan) { - entityReferenceStack.clear(); - collectionReferenceStack.clear(); - } - - private EntityReference getCurrentEntityReference() { - return entityReferenceStack.peekFirst() == null ? null : entityReferenceStack.peekFirst(); - } - - private CollectionReference getCurrentCollectionReference() { - return collectionReferenceStack.peekFirst() == null ? null : collectionReferenceStack.peekFirst(); - } - - private void pushToStack(Deque stack, T value) { - stack.push( value ); - } - - private void popFromStack(Deque stack, T expectedValue) { - T poppedValue = stack.pop(); - if ( poppedValue != expectedValue ) { - throw new WalkingException( - String.format( - "Unexpected value from stack. Expected=[%s]; instead it was [%s].", - expectedValue, - poppedValue - ) - ); - } - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryImpl.java deleted file mode 100755 index eb1612a68d..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/EntityLoadQueryImpl.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.internal; -import java.util.Collections; -import java.util.List; - -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.MappingException; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.plan.spi.EntityReturn; -import org.hibernate.loader.spi.JoinableAssociation; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; - -/** - * Represents an load query for fetching an entity, used for generating SQL. - * - * This code is based on the SQL generation code originally in - * org.hibernate.loader.EntityJoinWalker. - * - * @author Gavin King - * @author Gail Badner - */ -public class EntityLoadQueryImpl extends AbstractEntityLoadQueryImpl { - - public EntityLoadQueryImpl( - EntityReturn entityReturn, - List associations) throws MappingException { - super( entityReturn, associations ); - } - - public String generateSql( - String[] uniqueKey, - int batchSize, - LockMode lockMode, - SessionFactoryImplementor factory, - LoadQueryAliasResolutionContext aliasResolutionContext) { - StringBuilder whereCondition = whereString( resolveEntityReturnAlias( aliasResolutionContext ), uniqueKey, batchSize ) - //include the discriminator and class-level where, but not filters - .append( getPersister().filterFragment( resolveEntityReturnAlias( aliasResolutionContext ), Collections.EMPTY_MAP ) ); - return generateSql( whereCondition.toString(), "", new LockOptions().setLockMode( lockMode ), factory, aliasResolutionContext ); - } - - protected String getComment() { - return "load " + getPersister().getEntityName(); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessorImpl.java deleted file mode 100644 index 64dedf44fc..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessorImpl.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.internal; - -import java.io.Serializable; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import org.jboss.logging.Logger; - -import org.hibernate.cfg.NotYetImplementedException; -import org.hibernate.dialect.pagination.LimitHelper; -import org.hibernate.engine.FetchStyle; -import org.hibernate.engine.spi.QueryParameters; -import org.hibernate.engine.spi.RowSelection; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.loader.plan.spi.CollectionFetch; -import org.hibernate.loader.plan.spi.CollectionReturn; -import org.hibernate.loader.plan.spi.EntityFetch; -import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.visit.LoadPlanVisitationStrategyAdapter; -import org.hibernate.loader.plan.spi.visit.LoadPlanVisitor; -import org.hibernate.loader.plan.spi.Return; -import org.hibernate.loader.spi.AfterLoadAction; -import org.hibernate.loader.spi.LoadPlanAdvisor; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; -import org.hibernate.loader.spi.ScrollableResultSetProcessor; -import org.hibernate.loader.spi.ResultSetProcessor; -import org.hibernate.persister.collection.CollectionPersister; -import org.hibernate.pretty.MessageHelper; -import org.hibernate.transform.ResultTransformer; - -/** - * @author Steve Ebersole - */ -public class ResultSetProcessorImpl implements ResultSetProcessor { - private static final Logger LOG = Logger.getLogger( ResultSetProcessorImpl.class ); - - private final LoadPlan baseLoadPlan; - - private final boolean hadSubselectFetches; - - public ResultSetProcessorImpl(LoadPlan loadPlan) { - this.baseLoadPlan = loadPlan; - - LocalVisitationStrategy strategy = new LocalVisitationStrategy(); - LoadPlanVisitor.visit( loadPlan, strategy ); - this.hadSubselectFetches = strategy.hadSubselectFetches; - } - - @Override - public ScrollableResultSetProcessor toOnDemandForm() { - // todo : implement - throw new NotYetImplementedException(); - } - - @Override - public List extractResults( - LoadPlanAdvisor loadPlanAdvisor, - ResultSet resultSet, - final SessionImplementor session, - QueryParameters queryParameters, - NamedParameterContext namedParameterContext, - LoadQueryAliasResolutionContext aliasResolutionContext, - boolean returnProxies, - boolean readOnly, - ResultTransformer forcedResultTransformer, - List afterLoadActionList) throws SQLException { - - final LoadPlan loadPlan = loadPlanAdvisor.advise( this.baseLoadPlan ); - if ( loadPlan == null ) { - throw new IllegalStateException( "LoadPlanAdvisor returned null" ); - } - - handlePotentiallyEmptyCollectionRootReturns( loadPlan, queryParameters.getCollectionKeys(), resultSet, session ); - - final int maxRows; - final RowSelection selection = queryParameters.getRowSelection(); - if ( LimitHelper.hasMaxRows( selection ) ) { - maxRows = selection.getMaxRows(); - LOG.tracef( "Limiting ResultSet processing to just %s rows", maxRows ); - } - else { - maxRows = Integer.MAX_VALUE; - } - - final ResultSetProcessingContextImpl context = new ResultSetProcessingContextImpl( - resultSet, - session, - loadPlan, - readOnly, -// true, // use optional entity key? for now, always say yes - false, // use optional entity key? actually for now always say no since in the simple test cases true causes failures because there is no optional key - queryParameters, - namedParameterContext, - aliasResolutionContext, - hadSubselectFetches - ); - - final List loadResults = new ArrayList(); - - final int rootReturnCount = loadPlan.getReturns().size(); - - LOG.trace( "Processing result set" ); - int count; - for ( count = 0; count < maxRows && resultSet.next(); count++ ) { - LOG.debugf( "Starting ResultSet row #%s", count ); - - Object logicalRow; - if ( rootReturnCount == 1 ) { - loadPlan.getReturns().get( 0 ).hydrate( resultSet, context ); - loadPlan.getReturns().get( 0 ).resolve( resultSet, context ); - - logicalRow = loadPlan.getReturns().get( 0 ).read( resultSet, context ); - context.readCollectionElements( new Object[] { logicalRow } ); - } - else { - for ( Return rootReturn : loadPlan.getReturns() ) { - rootReturn.hydrate( resultSet, context ); - } - for ( Return rootReturn : loadPlan.getReturns() ) { - rootReturn.resolve( resultSet, context ); - } - - logicalRow = new Object[ rootReturnCount ]; - int pos = 0; - for ( Return rootReturn : loadPlan.getReturns() ) { - ( (Object[]) logicalRow )[pos] = rootReturn.read( resultSet, context ); - pos++; - } - context.readCollectionElements( (Object[]) logicalRow ); - } - - // todo : apply transformers here? - - loadResults.add( logicalRow ); - - context.finishUpRow(); - } - - LOG.tracev( "Done processing result set ({0} rows)", count ); - - context.finishUp( afterLoadActionList ); - - session.getPersistenceContext().initializeNonLazyCollections(); - - return loadResults; - } - - - private void handlePotentiallyEmptyCollectionRootReturns( - LoadPlan loadPlan, - Serializable[] collectionKeys, - ResultSet resultSet, - SessionImplementor session) { - if ( collectionKeys == null ) { - // this is not a collection initializer (and empty collections will be detected by looking for - // the owner's identifier in the result set) - return; - } - - // this is a collection initializer, so we must create a collection - // for each of the passed-in keys, to account for the possibility - // that the collection is empty and has no rows in the result set - // - // todo : move this inside CollectionReturn ? - CollectionPersister persister = ( (CollectionReturn) loadPlan.getReturns().get( 0 ) ).getCollectionPersister(); - for ( Serializable key : collectionKeys ) { - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Preparing collection intializer : %s", - MessageHelper.collectionInfoString( persister, key, session.getFactory() ) - ); - session.getPersistenceContext() - .getLoadContexts() - .getCollectionLoadContext( resultSet ) - .getLoadingCollection( persister, key ); - } - } - } - - - private class LocalVisitationStrategy extends LoadPlanVisitationStrategyAdapter { - private boolean hadSubselectFetches = false; - - @Override - public void startingEntityFetch(EntityFetch entityFetch) { -// only collections are currently supported for subselect fetching. -// hadSubselectFetches = hadSubselectFetches -// | entityFetch.getFetchStrategy().getStyle() == FetchStyle.SUBSELECT; - } - - @Override - public void startingCollectionFetch(CollectionFetch collectionFetch) { - hadSubselectFetches = hadSubselectFetches - | collectionFetch.getFetchStrategy().getStyle() == FetchStyle.SUBSELECT; - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/LoadQueryAliasResolutionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java similarity index 53% rename from hibernate-core/src/main/java/org/hibernate/loader/internal/LoadQueryAliasResolutionContextImpl.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java index cd6f42ae80..6f514622dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/LoadQueryAliasResolutionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AliasResolutionContextImpl.java @@ -21,140 +21,179 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.internal; +package org.hibernate.loader.plan.exec.internal; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.hql.internal.NameGenerator; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.CollectionAliases; import org.hibernate.loader.DefaultEntityAliases; import org.hibernate.loader.EntityAliases; import org.hibernate.loader.GeneratedCollectionAliases; +import org.hibernate.loader.plan.spi.BidirectionalEntityFetch; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.exec.spi.CollectionReferenceAliases; +import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionReference; -import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CompositeElementGraph; +import org.hibernate.loader.plan.spi.CompositeFetch; import org.hibernate.loader.plan.spi.CompositeIndexGraph; import org.hibernate.loader.plan.spi.EntityReference; -import org.hibernate.loader.plan.spi.EntityReturn; import org.hibernate.loader.plan.spi.Fetch; +import org.hibernate.loader.plan.spi.FetchOwner; import org.hibernate.loader.plan.spi.Return; import org.hibernate.loader.plan.spi.ScalarReturn; +import org.hibernate.loader.plan.spi.SourceQualifiable; import org.hibernate.loader.spi.JoinableAssociation; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; +import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.type.EntityType; /** * Provides aliases that are used by load queries and ResultSet processors. * * @author Gail Badner + * @author Steve Ebersole */ -public class LoadQueryAliasResolutionContextImpl implements LoadQueryAliasResolutionContext { - private final Map aliasesByReturn; - private final Map aliasesByEntityReference = - new HashMap(); +public class AliasResolutionContextImpl implements AliasResolutionContext { + private final SessionFactoryImplementor sessionFactory; + + private final Map sourceAliasByReturnMap; + private final Map sourceQualifiersByReturnMap; + + private final Map aliasesByEntityReference = + new HashMap(); private final Map aliasesByCollectionReference = new HashMap(); private final Map aliasesByJoinableAssociation = new HashMap(); - private final SessionFactoryImplementor sessionFactory; - private int currentAliasSuffix = 0; + private int currentAliasSuffix; + private int currentTableAliasUniqueness; - public LoadQueryAliasResolutionContextImpl( + /** + * Constructs a AliasResolutionContextImpl without any source aliases. This form is used in + * non-query (HQL, criteria, etc) contexts. + * + * @param sessionFactory The session factory + */ + public AliasResolutionContextImpl(SessionFactoryImplementor sessionFactory) { + this( sessionFactory, 0 ); + } + + /** + * Constructs a AliasResolutionContextImpl without any source aliases. This form is used in + * non-query (HQL, criteria, etc) contexts. + * + * @param sessionFactory The session factory + * @param suffixSeed The seed value to use for generating the suffix used when generating SQL aliases. + */ + public AliasResolutionContextImpl(SessionFactoryImplementor sessionFactory, int suffixSeed) { + this( + sessionFactory, + suffixSeed, + Collections.emptyMap(), + Collections.emptyMap() + ); + } + + /** + * Constructs a AliasResolutionContextImpl with source aliases. See the notes on + * {@link org.hibernate.loader.plan.exec.spi.AliasResolutionContext#getSourceAlias(Return)} for discussion of "source aliases". + * + * @param sessionFactory The session factory + * @param suffixSeed The seed value to use for generating the suffix used when generating SQL aliases. + * @param sourceAliasByReturnMap Mapping of the source alias for each return (select-clause assigned alias). + * @param sourceQualifiersByReturnMap Mapping of source query qualifiers (from-clause assigned alias). + */ + public AliasResolutionContextImpl( SessionFactoryImplementor sessionFactory, int suffixSeed, - Map aliasesByReturn) { + Map sourceAliasByReturnMap, + Map sourceQualifiersByReturnMap) { this.sessionFactory = sessionFactory; this.currentAliasSuffix = suffixSeed; - - checkAliasesByReturn( aliasesByReturn ); - this.aliasesByReturn = new HashMap( aliasesByReturn ); + this.sourceAliasByReturnMap = new HashMap( sourceAliasByReturnMap ); + this.sourceQualifiersByReturnMap = new HashMap( sourceQualifiersByReturnMap ); } - private static void checkAliasesByReturn(Map aliasesByReturn) { - if ( aliasesByReturn == null || aliasesByReturn.size() == 0 ) { - throw new IllegalArgumentException( "No return aliases defined" ); + @Override + public String getSourceAlias(Return theReturn) { + return sourceAliasByReturnMap.get( theReturn ); + } + + @Override + public String[] resolveScalarColumnAliases(ScalarReturn scalarReturn) { + final int numberOfColumns = scalarReturn.getType().getColumnSpan( sessionFactory ); + + // if the scalar return was assigned an alias in the source query, use that as the basis for generating + // the SQL aliases + final String sourceAlias = getSourceAlias( scalarReturn ); + if ( sourceAlias != null ) { + // generate one based on the source alias + // todo : to do this properly requires dialect involvement ++ + // due to needing uniqueness even across identifier length based truncation; just truncating is + // *not* enough since truncated names might clash + // + // for now, don't even truncate... + return NameGenerator.scalarNames( sourceAlias, numberOfColumns ); } - for ( Map.Entry entry : aliasesByReturn.entrySet() ) { - final Return aReturn = entry.getKey(); - final String[] aliases = entry.getValue(); - if ( aReturn == null ) { - throw new IllegalArgumentException( "null key found in aliasesByReturn" ); - } - if ( aliases == null || aliases.length == 0 ) { - throw new IllegalArgumentException( - String.format( "No alias defined for [%s]", aReturn ) + else { + // generate one from scratch + return NameGenerator.scalarNames( currentAliasSuffix++, numberOfColumns ); + } + } + + @Override + public EntityReferenceAliases resolveAliases(EntityReference entityReference) { + EntityReferenceAliasesImpl aliases = aliasesByEntityReference.get( entityReference ); + if ( aliases == null ) { + if ( BidirectionalEntityFetch.class.isInstance( entityReference ) ) { + return resolveAliases( + ( (BidirectionalEntityFetch) entityReference ).getTargetEntityReference() ); } - if ( ( aliases.length > 1 ) && - ( aReturn instanceof EntityReturn || aReturn instanceof CollectionReturn ) ) { - throw new IllegalArgumentException( String.format( "More than 1 alias defined for [%s]", aReturn ) ); - } - for ( String alias : aliases ) { - if ( StringHelper.isEmpty( alias ) ) { - throw new IllegalArgumentException( String.format( "An alias for [%s] is null or empty.", aReturn ) ); - } - } - } - } - - @Override - public String resolveEntityReturnAlias(EntityReturn entityReturn) { - return getAndCheckReturnAliasExists( entityReturn )[ 0 ]; - } - - @Override - public String resolveCollectionReturnAlias(CollectionReturn collectionReturn) { - return getAndCheckReturnAliasExists( collectionReturn )[ 0 ]; - } - - @Override - public String[] resolveScalarReturnAliases(ScalarReturn scalarReturn) { - throw new NotYetImplementedException( "Cannot resolve scalar column aliases yet." ); - } - - private String[] getAndCheckReturnAliasExists(Return aReturn) { - // There is already a check for the appropriate number of aliases stored in aliasesByReturn, - // so just check for existence here. - final String[] aliases = aliasesByReturn.get( aReturn ); - if ( aliases == null ) { - throw new IllegalStateException( - String.format( "No alias is defined for [%s]", aReturn ) + final EntityPersister entityPersister = entityReference.getEntityPersister(); + aliases = new EntityReferenceAliasesImpl( + createTableAlias( entityPersister ), + createEntityAliases( entityPersister ) ); + aliasesByEntityReference.put( entityReference, aliases ); } return aliases; } @Override - public String resolveEntityTableAlias(EntityReference entityReference) { - return getOrGenerateLoadQueryEntityAliases( entityReference ).tableAlias; + public CollectionReferenceAliases resolveAliases(CollectionReference collectionReference) { + LoadQueryCollectionAliasesImpl aliases = aliasesByCollectionReference.get( collectionReference ); + if ( aliases == null ) { + final CollectionPersister collectionPersister = collectionReference.getCollectionPersister(); + aliases = new LoadQueryCollectionAliasesImpl( + createTableAlias( collectionPersister.getRole() ), + collectionPersister.isManyToMany() + ? createTableAlias( collectionPersister.getRole() ) + : null, + createCollectionAliases( collectionPersister ), + createCollectionElementAliases( collectionPersister ) + ); + aliasesByCollectionReference.put( collectionReference, aliases ); + } + return aliases; } - @Override - public EntityAliases resolveEntityColumnAliases(EntityReference entityReference) { - return getOrGenerateLoadQueryEntityAliases( entityReference ).columnAliases; - } - @Override - public String resolveCollectionTableAlias(CollectionReference collectionReference) { - return getOrGenerateLoadQueryCollectionAliases( collectionReference ).tableAlias; - } - @Override - public CollectionAliases resolveCollectionColumnAliases(CollectionReference collectionReference) { - return getOrGenerateLoadQueryCollectionAliases( collectionReference ).collectionAliases; - } - @Override - public EntityAliases resolveCollectionElementColumnAliases(CollectionReference collectionReference) { - return getOrGenerateLoadQueryCollectionAliases( collectionReference ).collectionElementAliases; - } + + @Override public String resolveAssociationRhsTableAlias(JoinableAssociation joinableAssociation) { @@ -179,48 +218,29 @@ public class LoadQueryAliasResolutionContextImpl implements LoadQueryAliasResolu return Integer.toString( currentAliasSuffix++ ) + '_'; } - private LoadQueryEntityAliasesImpl getOrGenerateLoadQueryEntityAliases(EntityReference entityReference) { - LoadQueryEntityAliasesImpl aliases = aliasesByEntityReference.get( entityReference ); - if ( aliases == null ) { - final EntityPersister entityPersister = entityReference.getEntityPersister(); - aliases = new LoadQueryEntityAliasesImpl( - createTableAlias( entityPersister ), - createEntityAliases( entityPersister ) - ); - aliasesByEntityReference.put( entityReference, aliases ); - } - return aliases; - } - - private LoadQueryCollectionAliasesImpl getOrGenerateLoadQueryCollectionAliases(CollectionReference collectionReference) { - LoadQueryCollectionAliasesImpl aliases = aliasesByCollectionReference.get( collectionReference ); - if ( aliases == null ) { - final CollectionPersister collectionPersister = collectionReference.getCollectionPersister(); - aliases = new LoadQueryCollectionAliasesImpl( - createTableAlias( collectionPersister.getRole() ), - createCollectionAliases( collectionPersister ), - createCollectionElementAliases( collectionPersister ) - ); - aliasesByCollectionReference.put( collectionReference, aliases ); - } - return aliases; - } - private JoinableAssociationAliasesImpl getOrGenerateJoinAssocationAliases(JoinableAssociation joinableAssociation) { JoinableAssociationAliasesImpl aliases = aliasesByJoinableAssociation.get( joinableAssociation ); if ( aliases == null ) { final Fetch currentFetch = joinableAssociation.getCurrentFetch(); final String lhsAlias; - if ( EntityReference.class.isInstance( currentFetch.getOwner() ) ) { - lhsAlias = resolveEntityTableAlias( (EntityReference) currentFetch.getOwner() ); + if ( AnyFetch.class.isInstance( currentFetch ) ) { + throw new WalkingException( "Any type should never be joined!" ); + } + else if ( EntityReference.class.isInstance( currentFetch.getOwner() ) ) { + lhsAlias = resolveAliases( (EntityReference) currentFetch.getOwner() ).getTableAlias(); + } + else if ( CompositeFetch.class.isInstance( currentFetch.getOwner() ) ) { + lhsAlias = resolveAliases( + locateCompositeFetchEntityReferenceSource( (CompositeFetch) currentFetch.getOwner() ) + ).getTableAlias(); } else if ( CompositeElementGraph.class.isInstance( currentFetch.getOwner() ) ) { CompositeElementGraph compositeElementGraph = (CompositeElementGraph) currentFetch.getOwner(); - lhsAlias = resolveCollectionTableAlias( compositeElementGraph.getCollectionReference() ); + lhsAlias = resolveAliases( compositeElementGraph.getCollectionReference() ).getElementTableAlias(); } else if ( CompositeIndexGraph.class.isInstance( currentFetch.getOwner() ) ) { CompositeIndexGraph compositeIndexGraph = (CompositeIndexGraph) currentFetch.getOwner(); - lhsAlias = resolveCollectionTableAlias( compositeIndexGraph.getCollectionReference() ); + lhsAlias = resolveAliases( compositeIndexGraph.getCollectionReference() ).getElementTableAlias(); } else { throw new NotYetImplementedException( "Cannot determine LHS alias for FetchOwner." ); @@ -229,16 +249,16 @@ public class LoadQueryAliasResolutionContextImpl implements LoadQueryAliasResolu final String[] aliasedLhsColumnNames = currentFetch.toSqlSelectFragments( lhsAlias ); final String rhsAlias; if ( EntityReference.class.isInstance( currentFetch ) ) { - rhsAlias = resolveEntityTableAlias( (EntityReference) currentFetch ); + rhsAlias = resolveAliases( (EntityReference) currentFetch ).getTableAlias(); } else if ( CollectionReference.class.isInstance( joinableAssociation.getCurrentFetch() ) ) { - rhsAlias = resolveCollectionTableAlias( (CollectionReference) currentFetch ); + rhsAlias = resolveAliases( (CollectionReference) currentFetch ).getCollectionTableAlias(); } else { throw new NotYetImplementedException( "Cannot determine RHS alis for a fetch that is not an EntityReference or CollectionReference." ); } - // TODO: can't this be found in CollectionAliases or EntityAliases? should be moved to LoadQueryAliasResolutionContextImpl + // TODO: can't this be found in CollectionAliases or EntityAliases? should be moved to AliasResolutionContextImpl aliases = new JoinableAssociationAliasesImpl( lhsAlias, aliasedLhsColumnNames, rhsAlias ); aliasesByJoinableAssociation.put( joinableAssociation, aliases ); @@ -246,12 +266,24 @@ public class LoadQueryAliasResolutionContextImpl implements LoadQueryAliasResolu return aliases; } + private EntityReference locateCompositeFetchEntityReferenceSource(CompositeFetch composite) { + final FetchOwner owner = composite.getOwner(); + if ( EntityReference.class.isInstance( owner ) ) { + return (EntityReference) owner; + } + if ( CompositeFetch.class.isInstance( owner ) ) { + return locateCompositeFetchEntityReferenceSource( (CompositeFetch) owner ); + } + + throw new WalkingException( "Cannot resolve entity source for a CompositeFetch" ); + } + private String createTableAlias(EntityPersister entityPersister) { return createTableAlias( StringHelper.unqualifyEntityName( entityPersister.getEntityName() ) ); } private String createTableAlias(String name) { - return StringHelper.generateAlias( name ) + createSuffix(); + return StringHelper.generateAlias( name, currentTableAliasUniqueness++ ); } private EntityAliases createEntityAliases(EntityPersister entityPersister) { @@ -272,28 +304,43 @@ public class LoadQueryAliasResolutionContextImpl implements LoadQueryAliasResolu } } - private static class LoadQueryEntityAliasesImpl { - private final String tableAlias; - private final EntityAliases columnAliases; - - public LoadQueryEntityAliasesImpl(String tableAlias, EntityAliases columnAliases) { - this.tableAlias = tableAlias; - this.columnAliases = columnAliases; - } - } - - private static class LoadQueryCollectionAliasesImpl { + private static class LoadQueryCollectionAliasesImpl implements CollectionReferenceAliases { private final String tableAlias; + private final String manyToManyAssociationTableAlias; private final CollectionAliases collectionAliases; - private final EntityAliases collectionElementAliases; + private final EntityAliases entityElementAliases; public LoadQueryCollectionAliasesImpl( String tableAlias, + String manyToManyAssociationTableAlias, CollectionAliases collectionAliases, - EntityAliases collectionElementAliases) { + EntityAliases entityElementAliases) { this.tableAlias = tableAlias; + this.manyToManyAssociationTableAlias = manyToManyAssociationTableAlias; this.collectionAliases = collectionAliases; - this.collectionElementAliases = collectionElementAliases; + this.entityElementAliases = entityElementAliases; + } + + @Override + public String getCollectionTableAlias() { + return StringHelper.isNotEmpty( manyToManyAssociationTableAlias ) + ? manyToManyAssociationTableAlias + : tableAlias; + } + + @Override + public String getElementTableAlias() { + return tableAlias; + } + + @Override + public CollectionAliases getCollectionColumnAliases() { + return collectionAliases; + } + + @Override + public EntityAliases getEntityElementColumnAliases() { + return entityElementAliases; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java new file mode 100644 index 0000000000..985f6a2976 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/EntityReferenceAliasesImpl.java @@ -0,0 +1,51 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.internal; + +import org.hibernate.loader.EntityAliases; +import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; + +/** + * @author Gail Badner + * @author Steve Ebersole + */ +class EntityReferenceAliasesImpl implements EntityReferenceAliases { + private final String tableAlias; + private final EntityAliases columnAliases; + + public EntityReferenceAliasesImpl(String tableAlias, EntityAliases columnAliases) { + this.tableAlias = tableAlias; + this.columnAliases = columnAliases; + } + + @Override + public String getTableAlias() { + return tableAlias; + } + + @Override + public EntityAliases getColumnAliases() { + return columnAliases; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceReader.java new file mode 100644 index 0000000000..bf340f853f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReferenceReader.java @@ -0,0 +1,155 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.jboss.logging.Logger; + +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.spi.CollectionReferenceAliases; +import org.hibernate.loader.plan.spi.CollectionReference; +import org.hibernate.pretty.MessageHelper; + +/** + * @author Steve Ebersole + */ +public class CollectionReferenceReader { + private static final Logger log = CoreLogging.logger( CollectionReferenceReader.class ); + + private final CollectionReference collectionReference; + + public CollectionReferenceReader(CollectionReference collectionReference) { + this.collectionReference = collectionReference; + } + + public void finishUpRow(ResultSet resultSet, ResultSetProcessingContextImpl context) { + final CollectionReferenceAliases aliases = context.getAliasResolutionContext().resolveAliases( + collectionReference + ); + + try { + // read the collection key for this reference for the current row. + final PersistenceContext persistenceContext = context.getSession().getPersistenceContext(); + final Serializable collectionRowKey = (Serializable) collectionReference.getCollectionPersister().readKey( + resultSet, + aliases.getCollectionColumnAliases().getSuffixedKeyAliases(), + context.getSession() + ); + + if ( collectionRowKey != null ) { + // we found a collection element in the result set + + if ( log.isDebugEnabled() ) { + log.debugf( + "Found row of collection: %s", + MessageHelper.collectionInfoString( + collectionReference.getCollectionPersister(), + collectionRowKey, + context.getSession().getFactory() + ) + ); + } + + Object collectionOwner = findCollectionOwner( collectionRowKey, resultSet, context ); + + PersistentCollection rowCollection = persistenceContext.getLoadContexts() + .getCollectionLoadContext( resultSet ) + .getLoadingCollection( collectionReference.getCollectionPersister(), collectionRowKey ); + + if ( rowCollection != null ) { + rowCollection.readFrom( + resultSet, + collectionReference.getCollectionPersister(), + aliases.getCollectionColumnAliases(), + collectionOwner + ); + } + + } + else { + final Serializable optionalKey = findCollectionOwnerKey( context ); + if ( optionalKey != null ) { + // we did not find a collection element in the result set, so we + // ensure that a collection is created with the owner's identifier, + // since what we have is an empty collection + if ( log.isDebugEnabled() ) { + log.debugf( + "Result set contains (possibly empty) collection: %s", + MessageHelper.collectionInfoString( + collectionReference.getCollectionPersister(), + optionalKey, + context.getSession().getFactory() + ) + ); + } + // handle empty collection + persistenceContext.getLoadContexts() + .getCollectionLoadContext( resultSet ) + .getLoadingCollection( collectionReference.getCollectionPersister(), optionalKey ); + + } + } + // else no collection element, but also no owner + } + catch ( SQLException sqle ) { + // TODO: would be nice to have the SQL string that failed... + throw context.getSession().getFactory().getSQLExceptionHelper().convert( + sqle, + "could not read next row of results" + ); + } + + } + + protected Object findCollectionOwner( + Serializable collectionRowKey, + ResultSet resultSet, + ResultSetProcessingContextImpl context) { + final Object collectionOwner = context.getSession().getPersistenceContext().getCollectionOwner( + collectionRowKey, + collectionReference.getCollectionPersister() + ); + // todo : try org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.getOwnerProcessingState() ?? + // -- specifically to return its ResultSetProcessingContext.EntityReferenceProcessingState#getEntityInstance() + if ( collectionOwner == null ) { + //TODO: This is assertion is disabled because there is a bug that means the + // original owner of a transient, uninitialized collection is not known + // if the collection is re-referenced by a different object associated + // with the current Session + //throw new AssertionFailure("bug loading unowned collection"); + } + return collectionOwner; + } + + protected Serializable findCollectionOwnerKey(ResultSetProcessingContext context) { + return null; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReturnReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReturnReader.java new file mode 100644 index 0000000000..c71bfbec54 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/CollectionReturnReader.java @@ -0,0 +1,74 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ReturnReader; +import org.hibernate.loader.plan.spi.CollectionReturn; + +/** + * @author Steve Ebersole + */ +public class CollectionReturnReader extends CollectionReferenceReader implements ReturnReader { + private final CollectionReturn collectionReturn; + + public CollectionReturnReader(CollectionReturn collectionReturn) { + super( collectionReturn ); + this.collectionReturn = collectionReturn; + } + + @Override + protected Object findCollectionOwner( + Serializable collectionRowKey, + ResultSet resultSet, + ResultSetProcessingContextImpl context) { + if ( context.shouldUseOptionalEntityInformation() ) { + final Object optionalEntityInstance = context.getQueryParameters().getOptionalObject(); + if ( optionalEntityInstance != null ) { + return optionalEntityInstance; + } + } + return super.findCollectionOwner( collectionRowKey, resultSet, context ); + } + + @Override + protected Serializable findCollectionOwnerKey(ResultSetProcessingContext context) { + final EntityKey entityKey = context.shouldUseOptionalEntityInformation() + ? ResultSetProcessorHelper.getOptionalObjectKey( context.getQueryParameters(), context.getSession() ) + : null; + return entityKey == null + ? super.findCollectionOwnerKey( context ) + : entityKey; + } + + @Override + public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityIdentifierReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityIdentifierReader.java new file mode 100644 index 0000000000..5000b10e34 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityIdentifierReader.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; + +/** + * Identifiers are read from the ResultSet in 2 distinct phases: + *

    + *
  1. + * First we hydrate the identifier values (see {@link #hydrate}). During this "phase" 2 things happen: + *
      + *
    1. + * Any "optional identifier" specified on QueryParameters is considered. If the "optional identifier" + * is to be used for this identifier read, it is used to build an EntityKey which is associated with + * the {@link ResultSetProcessingContext.EntityReferenceProcessingState} for the EntityReference under + * {@link ResultSetProcessingContext.EntityReferenceProcessingState#registerEntityKey} + *
    2. + *
    3. + * All other id values are hydrated from the ResultSet. Those hydrated values are then registered + * with the {@link ResultSetProcessingContext.EntityReferenceProcessingState} for the EntityReference + * under {@link ResultSetProcessingContext.EntityReferenceProcessingState#registerIdentifierHydratedForm} + *
    4. + *
    + *
  2. + *
  3. + * Then we resolve the identifier. This is again a 2 step process: + *
      + *
    1. + * For all fetches that "come from" an identifier (key-many-to-ones), we fully hydrate those entities + *
    2. + *
    3. + * We then call resolve on root identifier type, and use that to build an EntityKey,which is then + * registered with the {@link ResultSetProcessingContext.EntityReferenceProcessingState} for the + * EntityReference whose identifier we are reading under + * {@link ResultSetProcessingContext.EntityReferenceProcessingState#registerEntityKey} + *
    4. + *
    + *
  4. + *
+ * + * @author Steve Ebersole + */ +public interface EntityIdentifierReader { + /** + * Hydrate the entity identifier. Perform the first phase outlined above. + * + * @param resultSet The ResultSet + * @param context The processing context + * + * @throws java.sql.SQLException Problem accessing ResultSet + */ + public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; + + /** + * Resolve the entity identifier. Perform the second phase outlined above. + * + * @param resultSet The ResultSet + * @param context The processing context + * + * @throws java.sql.SQLException Problem accessing ResultSet + */ + public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityIdentifierReaderImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityIdentifierReaderImpl.java new file mode 100644 index 0000000000..254612bebf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityIdentifierReaderImpl.java @@ -0,0 +1,326 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.spi.CompositeFetch; +import org.hibernate.loader.plan.spi.EntityFetch; +import org.hibernate.loader.plan.spi.EntityReference; +import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.loader.plan.spi.Fetch; +import org.hibernate.loader.plan.spi.FetchOwner; +import org.hibernate.persister.spi.HydratedCompoundValueHandler; +import org.hibernate.persister.walking.internal.FetchStrategyHelper; +import org.hibernate.persister.walking.spi.WalkingException; +import org.hibernate.type.Type; + +import static org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.*; + +/** + * Encapsulates the logic for reading a single entity identifier from a JDBC ResultSet, including support for fetches + * that are part of the identifier. + * + * @author Steve Ebersole + */ +class EntityIdentifierReaderImpl implements EntityIdentifierReader { + private static final Logger log = CoreLogging.logger( EntityIdentifierReaderImpl.class ); + + private final EntityReference entityReference; + + private List identifierFetchReaders; + + private final boolean isReturn; + private final Type identifierType; + + /** + * Creates a delegate capable of performing the reading of an entity identifier + * + * @param entityReference The entity reference for which we will be reading the identifier. + */ + EntityIdentifierReaderImpl(EntityReference entityReference) { + this.entityReference = entityReference; + this.isReturn = EntityReturn.class.isInstance( entityReference ); + this.identifierType = entityReference.getEntityPersister().getIdentifierType(); + + identifierFetchReaders = collectIdentifierFetchReaders(); + } + + private List collectIdentifierFetchReaders() { + if ( ! identifierType.isComponentType() ) { + return Collections.emptyList(); + } + final Fetch[] fetches = entityReference.getIdentifierDescription().getFetches(); + if ( fetches == null || fetches.length == 0 ) { + return Collections.emptyList(); + } + + final List readers = new ArrayList(); + for ( Fetch fetch : fetches ) { + collectIdentifierFetchReaders( readers, fetch ); + } + return readers; + } + + private void collectIdentifierFetchReaders(List readers, Fetch fetch) { + if ( CompositeFetch.class.isInstance( fetch ) ) { + for ( Fetch subFetch : ( (CompositeFetch) fetch).getFetches() ) { + collectIdentifierFetchReaders( readers, subFetch ); + } + } + else if ( ! EntityFetch.class.isInstance( fetch ) ) { + throw new IllegalStateException( + String.format( + "non-entity (and non-composite) fetch [%s] was found as part of entity identifier : %s", + fetch, + entityReference.getEntityPersister().getEntityName() + ) + ); + } + else { + final EntityReference fetchedEntityReference = (EntityReference) fetch; + readers.add( new EntityReferenceReader( fetchedEntityReference ) ); + } + } + + @Override + public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference ); + + // if the entity reference we are hydrating is a Return, it is possible that its EntityKey is + // supplied by the QueryParameter optional entity information + if ( context.shouldUseOptionalEntityInformation() ) { + if ( isReturn ) { + final EntityKey entityKey = ResultSetProcessorHelper.getOptionalObjectKey( + context.getQueryParameters(), + context.getSession() + ); + + if ( entityKey != null ) { + processingState.registerEntityKey( entityKey ); + return; + } + } + } + + // get any previously registered identifier hydrated-state + Object identifierHydratedForm = processingState.getIdentifierHydratedForm(); + if ( identifierHydratedForm == null ) { + // if there is none, read it from the result set + identifierHydratedForm = readIdentifierHydratedState( resultSet, context ); + + // broadcast the fact that a hydrated identifier value just became associated with + // this entity reference + processingState.registerIdentifierHydratedForm( identifierHydratedForm ); +// hydrateIdentifierFetchIdentifiers( resultSet, context, identifierHydratedForm ); + for ( EntityReferenceReader reader : identifierFetchReaders ) { + reader.hydrateIdentifier( resultSet, context ); + } + } + } + + /** + * Read the identifier state for the entity reference for the currently processing row in the ResultSet + * + * @param resultSet The ResultSet being processed + * @param context The processing context + * + * @return The hydrated state + * + * @throws java.sql.SQLException Indicates a problem accessing the ResultSet + */ + private Object readIdentifierHydratedState(ResultSet resultSet, ResultSetProcessingContext context) + throws SQLException { +// if ( EntityReturn.class.isInstance( entityReference ) ) { +// // if there is a "optional entity key" associated with the context it would pertain to this +// // entity reference, because it is the root return. +// final EntityKey suppliedEntityKey = context.getSuppliedOptionalEntityKey(); +// if ( suppliedEntityKey != null ) { +// return suppliedEntityKey.getIdentifier(); +// } +// } + + // Otherwise, read it from the ResultSet + final String[] columnNames; + if ( EntityFetch.class.isInstance( entityReference ) + && !FetchStrategyHelper.isJoinFetched( ((EntityFetch) entityReference).getFetchStrategy() ) ) { + final EntityFetch fetch = (EntityFetch) entityReference; + final FetchOwner fetchOwner = fetch.getOwner(); + if ( EntityReference.class.isInstance( fetchOwner ) ) { + throw new NotYetImplementedException(); +// final EntityReference ownerEntityReference = (EntityReference) fetchOwner; +// final EntityAliases ownerEntityAliases = context.getAliasResolutionContext() +// .resolveEntityColumnAliases( ownerEntityReference ); +// final int propertyIndex = ownerEntityReference.getEntityPersister() +// .getEntityMetamodel() +// .getPropertyIndex( fetch.getOwnerPropertyName() ); +// columnNames = ownerEntityAliases.getSuffixedPropertyAliases()[ propertyIndex ]; + } + else { + // todo : better message here... + throw new WalkingException( "Cannot locate association column names" ); + } + } + else { + columnNames = context.getAliasResolutionContext() + .resolveAliases( entityReference ) + .getColumnAliases() + .getSuffixedKeyAliases(); + } + + try { + return entityReference.getEntityPersister().getIdentifierType().hydrate( + resultSet, + columnNames, + context.getSession(), + null + ); + } + catch (Exception e) { + throw new HibernateException( + "Encountered problem trying to hydrate identifier for entity [" + + entityReference.getEntityPersister() + "]", + e + + ); + } + } + +// /** +// * Hydrate the identifiers of all fetches that are part of this entity reference's identifier (key-many-to-one). +// * +// * @param resultSet The ResultSet +// * @param context The processing context +// * @param hydratedIdentifierState The hydrated identifier state of the entity reference. We can extract the +// * fetch identifier's hydrated state from there if available, without having to read the Result (which can +// * be a performance problem on some drivers). +// */ +// private void hydrateIdentifierFetchIdentifiers( +// ResultSet resultSet, +// ResultSetProcessingContext context, +// Object hydratedIdentifierState) throws SQLException { +// // for all fetches that are part of our identifier... +// for ( Fetch fetch : entityReference.getIdentifierDescription().getFetches() ) { +// hydrateIdentifierFetchIdentifier( resultSet, context, fetch, hydratedIdentifierState ); +// } +// } +// +// private void hydrateIdentifierFetchIdentifier( +// ResultSet resultSet, +// ResultSetProcessingContext context, +// Fetch fetch, +// Object hydratedIdentifierState) throws SQLException { +// if ( CompositeFetch.class.isInstance( fetch ) ) { +// for ( Fetch subFetch : ( (CompositeFetch) fetch).getFetches() ) { +// hydrateIdentifierFetchIdentifier( resultSet, context, subFetch, hydratedIdentifierState ); +// } +// } +// else if ( ! EntityFetch.class.isInstance( fetch ) ) { +// throw new NotYetImplementedException( "Cannot hydrate identifier Fetch that is not an EntityFetch" ); +// } +// else { +// final EntityFetch entityFetch = (EntityFetch) fetch; +// final EntityReferenceProcessingState fetchProcessingState = context.getProcessingState( entityFetch ); +// +// // if the identifier for the fetch was already hydrated, nothing to do +// if ( fetchProcessingState.getIdentifierHydratedForm() != null ) { +// return; +// } +// +// // we can either hydrate the fetch's identifier from the incoming 'hydratedIdentifierState' (by +// // extracting the relevant portion using HydratedCompoundValueHandler) or we can +// // read it from the ResultSet +// if ( hydratedIdentifierState != null ) { +// final HydratedCompoundValueHandler hydratedStateHandler = entityReference.getIdentifierDescription().getHydratedStateHandler( fetch ); +// if ( hydratedStateHandler != null ) { +// final Serializable extracted = (Serializable) hydratedStateHandler.extract( hydratedIdentifierState ); +// fetchProcessingState.registerIdentifierHydratedForm( extracted ); +// } +// } +// else { +// // Use a reader to hydrate the fetched entity. +// // +// // todo : Ideally these should be kept around +// new EntityReferenceReader( entityFetch ).hydrateIdentifier( resultSet, context ); +// } +// } +// } + + + public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + // resolve fetched state from the identifier first + for ( EntityReferenceReader reader : identifierFetchReaders ) { + reader.resolveEntityKey( resultSet, context ); + } + for ( EntityReferenceReader reader : identifierFetchReaders ) { + reader.hydrateEntityState( resultSet, context ); + } + + final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference ); + + // see if we already have an EntityKey associated with this EntityReference in the processing state. + // if we do, this should have come from the optional entity identifier... + final EntityKey entityKey = processingState.getEntityKey(); + if ( entityKey != null ) { + log.debugf( + "On call to EntityIdentifierReaderImpl#resolve [for %s], EntityKey was already known; " + + "should only happen on root returns with an optional identifier specified" + ); + return; + } + + // Look for the hydrated form + final Object identifierHydratedForm = processingState.getIdentifierHydratedForm(); + if ( identifierHydratedForm == null ) { + // we need to register the missing identifier, but that happens later after all readers have had a chance + // to resolve its EntityKey + return; + } + + final Type identifierType = entityReference.getEntityPersister().getIdentifierType(); + final Serializable resolvedId = (Serializable) identifierType.resolve( + identifierHydratedForm, + context.getSession(), + null + ); + if ( resolvedId != null ) { + processingState.registerEntityKey( + context.getSession().generateEntityKey( resolvedId, entityReference.getEntityPersister() ) + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceReader.java new file mode 100644 index 0000000000..1fe9c8daf3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReferenceReader.java @@ -0,0 +1,436 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.jboss.logging.Logger; + +import org.hibernate.LockMode; +import org.hibernate.StaleObjectStateException; +import org.hibernate.WrongClassException; +import org.hibernate.engine.internal.TwoPhaseLoad; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.EntityAliases; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases; +import org.hibernate.loader.plan.spi.EntityFetch; +import org.hibernate.loader.plan.spi.EntityReference; +import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Loadable; +import org.hibernate.persister.entity.UniqueKeyLoadable; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.type.CompositeType; +import org.hibernate.type.EntityType; +import org.hibernate.type.Type; +import org.hibernate.type.VersionType; + +import static org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.EntityReferenceProcessingState; + +/** + * @author Steve Ebersole + */ +public class EntityReferenceReader { + private static final Logger log = CoreLogging.logger( EntityReferenceReader.class ); + + private final EntityReference entityReference; + private final EntityIdentifierReader identifierReader; + + private final boolean isReturn; + + + protected EntityReferenceReader(EntityReference entityReference, EntityIdentifierReader identifierReader) { + this.entityReference = entityReference; + this.identifierReader = identifierReader; + + this.isReturn = EntityReturn.class.isInstance( entityReference ); + } + + public EntityReferenceReader(EntityReference entityReference) { + this( entityReference, new EntityIdentifierReaderImpl( entityReference ) ); + } + + public EntityReference getEntityReference() { + return entityReference; + } + + public void hydrateIdentifier(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + identifierReader.hydrate( resultSet, context ); + } + + public void resolveEntityKey(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + identifierReader.resolve( resultSet, context ); + } + + public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContext context) { + // hydrate the entity reference. at this point it is expected that + + final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference ); + + // If there is no identifier for this entity reference for this row, nothing to do + if ( processingState.isMissingIdentifier() ) { + handleMissingIdentifier( context ); + return; + } + + // make sure we have the EntityKey + final EntityKey entityKey = processingState.getEntityKey(); + if ( entityKey == null ) { + handleMissingIdentifier( context ); + return; + } + + // Have we already hydrated this entity's state? + if ( processingState.getEntityInstance() != null ) { + return; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // In getting here, we know that: + // 1) We need to hydrate the entity state + // 2) We have a valid EntityKey for the entity + + // see if we have an existing entry in the session for this EntityKey + final Object existing = context.getSession().getEntityUsingInterceptor( entityKey ); + if ( existing != null ) { + // It is previously associated with the Session, perform some checks + if ( ! entityReference.getEntityPersister().isInstance( existing ) ) { + throw new WrongClassException( + "loaded object was of wrong class " + existing.getClass(), + entityKey.getIdentifier(), + entityReference.getEntityPersister().getEntityName() + ); + } + checkVersion( resultSet, context, entityKey, existing ); + + // use the existing association as the hydrated state + processingState.registerEntityInstance( existing ); + return; + } + + // Otherwise, we need to load it from the ResultSet... + + // determine which entity instance to use. Either the supplied one, or instantiate one + Object optionalEntityInstance = null; + if ( isReturn && context.shouldUseOptionalEntityInformation() ) { + final EntityKey optionalEntityKey = ResultSetProcessorHelper.getOptionalObjectKey( + context.getQueryParameters(), + context.getSession() + ); + if ( optionalEntityKey != null ) { + if ( optionalEntityKey.equals( entityKey ) ) { + optionalEntityInstance = context.getQueryParameters().getOptionalObject(); + } + } + } + + final String concreteEntityTypeName = getConcreteEntityTypeName( resultSet, context, entityKey ); + + final Object entityInstance = optionalEntityInstance != null + ? optionalEntityInstance + : context.getSession().instantiate( concreteEntityTypeName, entityKey.getIdentifier() ); + + processingState.registerEntityInstance( entityInstance ); + + // need to hydrate it. + // grab its state from the ResultSet and keep it in the Session + // (but don't yet initialize the object itself) + // note that we acquire LockMode.READ even if it was not requested + log.trace( "hydrating entity state" ); + final LockMode requestedLockMode = context.resolveLockMode( entityReference ); + final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE + ? LockMode.READ + : requestedLockMode; + + loadFromResultSet( + resultSet, + context, + entityInstance, + concreteEntityTypeName, + entityKey, + lockModeToAcquire + ); + } + + private void handleMissingIdentifier(ResultSetProcessingContext context) { + if ( EntityFetch.class.isInstance( entityReference ) ) { + final EntityFetch fetch = (EntityFetch) entityReference; + final EntityType fetchedType = fetch.getFetchedType(); + if ( ! fetchedType.isOneToOne() ) { + return; + } + + final EntityReferenceProcessingState fetchOwnerState = context.getOwnerProcessingState( fetch ); + if ( fetchOwnerState == null ) { + throw new IllegalStateException( "Could not locate fetch owner state" ); + } + + final EntityKey ownerEntityKey = fetchOwnerState.getEntityKey(); + if ( ownerEntityKey == null ) { + throw new IllegalStateException( "Could not locate fetch owner EntityKey" ); + } + + context.getSession().getPersistenceContext().addNullProperty( + ownerEntityKey, + fetchedType.getPropertyName() + ); + } + } + + private void loadFromResultSet( + ResultSet resultSet, + ResultSetProcessingContext context, + Object entityInstance, + String concreteEntityTypeName, + EntityKey entityKey, + LockMode lockModeToAcquire) { + final Serializable id = entityKey.getIdentifier(); + + // Get the persister for the _subclass_ + final Loadable concreteEntityPersister = (Loadable) context.getSession().getFactory().getEntityPersister( concreteEntityTypeName ); + + if ( log.isTraceEnabled() ) { + log.tracev( + "Initializing object from ResultSet: {0}", + MessageHelper.infoString( + concreteEntityPersister, + id, + context.getSession().getFactory() + ) + ); + } + + // add temp entry so that the next step is circular-reference + // safe - only needed because some types don't take proper + // advantage of two-phase-load (esp. components) + TwoPhaseLoad.addUninitializedEntity( + entityKey, + entityInstance, + concreteEntityPersister, + lockModeToAcquire, + !context.getLoadPlan().areLazyAttributesForceFetched(), + context.getSession() + ); + + final EntityPersister rootEntityPersister = context.getSession().getFactory().getEntityPersister( + concreteEntityPersister.getRootEntityName() + ); + final EntityReferenceAliases aliases = context.getAliasResolutionContext().resolveAliases( entityReference ); + final Object[] values; + try { + values = concreteEntityPersister.hydrate( + resultSet, + id, + entityInstance, + (Loadable) entityReference.getEntityPersister(), + concreteEntityPersister == rootEntityPersister + ? aliases.getColumnAliases().getSuffixedPropertyAliases() + : aliases.getColumnAliases().getSuffixedPropertyAliases( concreteEntityPersister ), + context.getLoadPlan().areLazyAttributesForceFetched(), + context.getSession() + ); + + context.getProcessingState( entityReference ).registerHydratedState( values ); + } + catch (SQLException e) { + throw context.getSession().getFactory().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Could not read entity state from ResultSet : " + entityKey + ); + } + + final Object rowId; + try { + rowId = concreteEntityPersister.hasRowId() ? resultSet.getObject( aliases.getColumnAliases().getRowIdAlias() ) : null; + } + catch (SQLException e) { + throw context.getSession().getFactory().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Could not read entity row-id from ResultSet : " + entityKey + ); + } + + final EntityType entityType = EntityFetch.class.isInstance( entityReference ) + ? ( (EntityFetch) entityReference ).getFetchedType() + : entityReference.getEntityPersister().getEntityMetamodel().getEntityType(); + + if ( entityType != null ) { + String ukName = entityType.getRHSUniqueKeyPropertyName(); + if ( ukName != null ) { + final int index = ( (UniqueKeyLoadable) concreteEntityPersister ).getPropertyIndex( ukName ); + final Type type = concreteEntityPersister.getPropertyTypes()[index]; + + // polymorphism not really handled completely correctly, + // perhaps...well, actually its ok, assuming that the + // entity name used in the lookup is the same as the + // the one used here, which it will be + + EntityUniqueKey euk = new EntityUniqueKey( + entityReference.getEntityPersister().getEntityName(), + ukName, + type.semiResolve( values[index], context.getSession(), entityInstance ), + type, + concreteEntityPersister.getEntityMode(), + context.getSession().getFactory() + ); + context.getSession().getPersistenceContext().addEntity( euk, entityInstance ); + } + } + + TwoPhaseLoad.postHydrate( + concreteEntityPersister, + id, + values, + rowId, + entityInstance, + lockModeToAcquire, + !context.getLoadPlan().areLazyAttributesForceFetched(), + context.getSession() + ); + + context.registerHydratedEntity( entityReference, entityKey, entityInstance ); + } + + private String getConcreteEntityTypeName( + ResultSet resultSet, + ResultSetProcessingContext context, + EntityKey entityKey) { + final Loadable loadable = (Loadable) entityReference.getEntityPersister(); + if ( ! loadable.hasSubclasses() ) { + return entityReference.getEntityPersister().getEntityName(); + } + + final Object discriminatorValue; + try { + discriminatorValue = loadable.getDiscriminatorType().nullSafeGet( + resultSet, + context.getAliasResolutionContext().resolveAliases( entityReference ).getColumnAliases().getSuffixedDiscriminatorAlias(), + context.getSession(), + null + ); + } + catch (SQLException e) { + throw context.getSession().getFactory().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Could not read discriminator value from ResultSet" + ); + } + + final String result = loadable.getSubclassForDiscriminatorValue( discriminatorValue ); + + if ( result == null ) { + // whoops! we got an instance of another class hierarchy branch + throw new WrongClassException( + "Discriminator: " + discriminatorValue, + entityKey.getIdentifier(), + entityReference.getEntityPersister().getEntityName() + ); + } + + return result; + } + + private void checkVersion( + ResultSet resultSet, + ResultSetProcessingContext context, + EntityKey entityKey, + Object existing) { + final LockMode requestedLockMode = context.resolveLockMode( entityReference ); + if ( requestedLockMode != LockMode.NONE ) { + final LockMode currentLockMode = context.getSession().getPersistenceContext().getEntry( existing ).getLockMode(); + final boolean isVersionCheckNeeded = entityReference.getEntityPersister().isVersioned() + && currentLockMode.lessThan( requestedLockMode ); + + // we don't need to worry about existing version being uninitialized because this block isn't called + // by a re-entrant load (re-entrant loads *always* have lock mode NONE) + if ( isVersionCheckNeeded ) { + //we only check the version when *upgrading* lock modes + checkVersion( + context.getSession(), + resultSet, + entityReference.getEntityPersister(), + context.getAliasResolutionContext().resolveAliases( entityReference ).getColumnAliases(), + entityKey, + existing + ); + //we need to upgrade the lock mode to the mode requested + context.getSession().getPersistenceContext().getEntry( existing ).setLockMode( requestedLockMode ); + } + } + } + + private void checkVersion( + SessionImplementor session, + ResultSet resultSet, + EntityPersister persister, + EntityAliases entityAliases, + EntityKey entityKey, + Object entityInstance) { + final Object version = session.getPersistenceContext().getEntry( entityInstance ).getVersion(); + + if ( version != null ) { + //null version means the object is in the process of being loaded somewhere else in the ResultSet + VersionType versionType = persister.getVersionType(); + final Object currentVersion; + try { + currentVersion = versionType.nullSafeGet( + resultSet, + entityAliases.getSuffixedVersionAliases(), + session, + null + ); + } + catch (SQLException e) { + throw session.getFactory().getJdbcServices().getSqlExceptionHelper().convert( + e, + "Could not read version value from result set" + ); + } + + if ( !versionType.isEqual( version, currentVersion ) ) { + if ( session.getFactory().getStatistics().isStatisticsEnabled() ) { + session.getFactory().getStatisticsImplementor().optimisticFailure( persister.getEntityName() ); + } + throw new StaleObjectStateException( persister.getEntityName(), entityKey.getIdentifier() ); + } + } + } + + public void resolve(ResultSet resultSet, ResultSetProcessingContext context) { + //To change body of created methods use File | Settings | File Templates. + } + + public void finishUpRow(ResultSet resultSet, ResultSetProcessingContextImpl context) { + //To change body of created methods use File | Settings | File Templates. + } + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReturnReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReturnReader.java new file mode 100644 index 0000000000..0d5edb86af --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/EntityReturnReader.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.AssertionFailure; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ReturnReader; +import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.proxy.HibernateProxy; + +import static org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.EntityReferenceProcessingState; + +/** + * @author Steve Ebersole + */ +public class EntityReturnReader extends EntityReferenceReader implements ReturnReader { + private final EntityReturn entityReturn; + + public EntityReturnReader(EntityReturn entityReturn) { + super( entityReturn ); + this.entityReturn = entityReturn; + } + +// @Override +// public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { +// final EntityKey entityKey = getEntityKeyFromContext( context ); +// if ( entityKey != null ) { +// getIdentifierResolutionContext( context ).registerEntityKey( entityKey ); +// return; +// } +// +// entityReturn.getIdentifierDescription().hydrate( resultSet, context ); +// +// for ( Fetch fetch : entityReturn.getFetches() ) { +// if ( FetchStrategyHelper.isJoinFetched( fetch.getFetchStrategy() ) ) { +// fetch.hydrate( resultSet, context ); +// } +// } +// } + + private EntityReferenceProcessingState getIdentifierResolutionContext(ResultSetProcessingContext context) { + final ResultSetProcessingContext.EntityReferenceProcessingState entityReferenceProcessingState = context.getProcessingState( + entityReturn + ); + + if ( entityReferenceProcessingState == null ) { + throw new AssertionFailure( + String.format( + "Could not locate EntityReferenceProcessingState for root entity return [%s (%s)]", + entityReturn.getPropertyPath().getFullPath(), + entityReturn.getEntityPersister().getEntityName() + ) + ); + } + + return entityReferenceProcessingState; + } + +// @Override +// public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { +// final EntityReferenceProcessingState entityReferenceProcessingState = getIdentifierResolutionContext( context ); +// EntityKey entityKey = entityReferenceProcessingState.getEntityKey(); +// if ( entityKey != null ) { +// return; +// } +// +// entityKey = entityReturn.getIdentifierDescription().resolve( resultSet, context ); +// entityReferenceProcessingState.registerEntityKey( entityKey ); +// +// for ( Fetch fetch : entityReturn.getFetches() ) { +// if ( FetchStrategyHelper.isJoinFetched( fetch.getFetchStrategy() ) ) { +// fetch.resolve( resultSet, context ); +// } +// } +// } + + @Override + public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + final EntityReferenceProcessingState processingState = getIdentifierResolutionContext( context ); + + final EntityKey entityKey = processingState.getEntityKey(); + final Object entityInstance = context.getProcessingState( entityReturn ).getEntityInstance(); + + if ( context.shouldReturnProxies() ) { + final Object proxy = context.getSession().getPersistenceContext().proxyFor( + entityReturn.getEntityPersister(), + entityKey, + entityInstance + ); + if ( proxy != entityInstance ) { + ( (HibernateProxy) proxy ).getHibernateLazyInitializer().setImplementation( proxy ); + return proxy; + } + } + + return entityInstance; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/Helper.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/Helper.java new file mode 100644 index 0000000000..84da32cadd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/Helper.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import org.hibernate.loader.plan.spi.CompositeFetch; +import org.hibernate.loader.plan.spi.EntityElementGraph; +import org.hibernate.loader.plan.spi.EntityReference; +import org.hibernate.loader.plan.spi.FetchOwner; + +/** + * @author Steve Ebersole + */ +public class Helper { + /** + * Singleton access + */ + public static final Helper INSTANCE = new Helper(); + + private Helper() { + } + + public EntityReference findOwnerEntityReference(FetchOwner owner) { + if ( EntityReference.class.isInstance( owner ) ) { + return (EntityReference) owner; + } + else if ( CompositeFetch.class.isInstance( owner ) ) { + return findOwnerEntityReference( ( (CompositeFetch) owner).getOwner() ); + } + else if ( EntityElementGraph.class.isInstance( owner ) ) { + return ( (EntityElementGraph) owner ).getEntityReference(); + } + + throw new IllegalStateException( + "Could not locate owner's EntityReference : " + owner.getPropertyPath().getFullPath() + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/OneToOneFetchReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/OneToOneFetchReader.java new file mode 100644 index 0000000000..82bad7b476 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/OneToOneFetchReader.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import org.hibernate.loader.plan.spi.EntityFetch; +import org.hibernate.loader.plan.spi.EntityReference; + +/** + * @author Steve Ebersole + */ +public class OneToOneFetchReader extends EntityReferenceReader { + private final EntityReference ownerEntityReference; + + public OneToOneFetchReader(EntityFetch entityFetch, EntityReference ownerEntityReference) { + super( entityFetch, new OneToOneFetchIdentifierReader( entityFetch, ownerEntityReference ) ); + this.ownerEntityReference = ownerEntityReference; + } + + private static class OneToOneFetchIdentifierReader extends EntityIdentifierReaderImpl { + public OneToOneFetchIdentifierReader(EntityFetch oneToOne, EntityReference ownerEntityReference) { + super( oneToOne ); + } + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessingContextImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java similarity index 74% rename from hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessingContextImpl.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java index 940dab925d..30fad0710d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessingContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessingContextImpl.java @@ -21,26 +21,26 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.internal; +package org.hibernate.loader.plan.exec.process.internal; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.jboss.logging.Logger; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import org.hibernate.WrongClassException; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; @@ -53,16 +53,21 @@ import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PreLoadEvent; import org.hibernate.loader.CollectionAliases; import org.hibernate.loader.EntityAliases; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.exec.spi.LockModeResolver; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReturn; +import org.hibernate.loader.plan.spi.CompositeFetch; +import org.hibernate.loader.plan.spi.EntityFetch; import org.hibernate.loader.plan.spi.EntityReference; +import org.hibernate.loader.plan.spi.Fetch; +import org.hibernate.loader.plan.spi.FetchOwner; import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.loader.plan.spi.visit.LoadPlanVisitationStrategyAdapter; import org.hibernate.loader.plan.spi.visit.LoadPlanVisitor; import org.hibernate.loader.spi.AfterLoadAction; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; -import org.hibernate.loader.spi.ResultSetProcessingContext; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; @@ -82,46 +87,75 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex private final SessionImplementor session; private final LoadPlan loadPlan; private final boolean readOnly; + private final boolean shouldUseOptionalEntityInformation; + private final boolean forceFetchLazyAttributes; + private final boolean shouldReturnProxies; private final QueryParameters queryParameters; private final NamedParameterContext namedParameterContext; - private final LoadQueryAliasResolutionContext aliasResolutionContext; + private final AliasResolutionContext aliasResolutionContext; private final boolean hadSubselectFetches; - private final EntityKey dictatedRootEntityKey; - private List currentRowHydratedEntityRegistrationList; private Map> subselectLoadableEntityKeyMap; private List hydratedEntityRegistrationList; + private LockModeResolver lockModeResolverDelegate = new LockModeResolver() { + @Override + public LockMode resolveLockMode(EntityReference entityReference) { + return LockMode.NONE; + } + }; + + /** + * Builds a ResultSetProcessingContextImpl + * + * @param resultSet + * @param session + * @param loadPlan + * @param readOnly + * @param shouldUseOptionalEntityInformation There are times when the "optional entity information" on + * QueryParameters should be used and times when they should not. Collection initializers, batch loaders, etc + * are times when it should NOT be used. + * @param forceFetchLazyAttributes + * @param shouldReturnProxies + * @param queryParameters + * @param namedParameterContext + * @param aliasResolutionContext + * @param hadSubselectFetches + */ public ResultSetProcessingContextImpl( ResultSet resultSet, SessionImplementor session, LoadPlan loadPlan, boolean readOnly, - boolean useOptionalEntityKey, + boolean shouldUseOptionalEntityInformation, + boolean forceFetchLazyAttributes, + boolean shouldReturnProxies, QueryParameters queryParameters, NamedParameterContext namedParameterContext, - LoadQueryAliasResolutionContext aliasResolutionContext, + AliasResolutionContext aliasResolutionContext, boolean hadSubselectFetches) { this.resultSet = resultSet; this.session = session; this.loadPlan = loadPlan; this.readOnly = readOnly; + this.shouldUseOptionalEntityInformation = shouldUseOptionalEntityInformation; + this.forceFetchLazyAttributes = forceFetchLazyAttributes; + this.shouldReturnProxies = shouldReturnProxies; this.queryParameters = queryParameters; this.namedParameterContext = namedParameterContext; this.aliasResolutionContext = aliasResolutionContext; this.hadSubselectFetches = hadSubselectFetches; - if ( useOptionalEntityKey ) { - this.dictatedRootEntityKey = ResultSetProcessorHelper.getOptionalObjectKey( queryParameters, session ); - if ( this.dictatedRootEntityKey == null ) { - throw new HibernateException( "Unable to resolve optional entity-key" ); + if ( shouldUseOptionalEntityInformation ) { + if ( queryParameters.getOptionalId() != null ) { + // make sure we have only one return + if ( loadPlan.getReturns().size() > 1 ) { + throw new IllegalStateException( "Cannot specify 'optional entity' values with multi-return load plans" ); + } } } - else { - this.dictatedRootEntityKey = null; - } } @Override @@ -129,28 +163,48 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex return session; } + @Override + public boolean shouldUseOptionalEntityInformation() { + return shouldUseOptionalEntityInformation; + } + @Override public QueryParameters getQueryParameters() { return queryParameters; } @Override - public EntityKey getDictatedRootEntityKey() { - return dictatedRootEntityKey; + public boolean shouldReturnProxies() { + return shouldReturnProxies; } - private Map identifierResolutionContextMap; + @Override + public LoadPlan getLoadPlan() { + return loadPlan; + } @Override - public IdentifierResolutionContext getIdentifierResolutionContext(final EntityReference entityReference) { + public LockMode resolveLockMode(EntityReference entityReference) { + final LockMode lockMode = lockModeResolverDelegate.resolveLockMode( entityReference ); + return LockMode.NONE == lockMode ? LockMode.NONE : lockMode; + } + + private Map identifierResolutionContextMap; + + @Override + public EntityReferenceProcessingState getProcessingState(final EntityReference entityReference) { if ( identifierResolutionContextMap == null ) { - identifierResolutionContextMap = new HashMap(); + identifierResolutionContextMap = new IdentityHashMap(); } - IdentifierResolutionContext context = identifierResolutionContextMap.get( entityReference ); + + EntityReferenceProcessingState context = identifierResolutionContextMap.get( entityReference ); if ( context == null ) { - context = new IdentifierResolutionContext() { - private Object hydratedForm; + context = new EntityReferenceProcessingState() { + private boolean wasMissingIdentifier; + private Object identifierHydratedForm; private EntityKey entityKey; + private Object[] hydratedState; + private Object entityInstance; @Override public EntityReference getEntityReference() { @@ -158,23 +212,31 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex } @Override - public void registerHydratedForm(Object hydratedForm) { - if ( this.hydratedForm != null ) { - // this could be bad... + public void registerMissingIdentifier() { + if ( !EntityFetch.class.isInstance( entityReference ) ) { + throw new IllegalStateException( "Missing return row identifier" ); } - this.hydratedForm = hydratedForm; + ResultSetProcessingContextImpl.this.registerNonExists( (EntityFetch) entityReference ); + wasMissingIdentifier = true; } @Override - public Object getHydratedForm() { - return hydratedForm; + public boolean isMissingIdentifier() { + return wasMissingIdentifier; + } + + @Override + public void registerIdentifierHydratedForm(Object identifierHydratedForm) { + this.identifierHydratedForm = identifierHydratedForm; + } + + @Override + public Object getIdentifierHydratedForm() { + return identifierHydratedForm; } @Override public void registerEntityKey(EntityKey entityKey) { - if ( this.entityKey != null ) { - // again, could be trouble... - } this.entityKey = entityKey; } @@ -182,6 +244,26 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex public EntityKey getEntityKey() { return entityKey; } + + @Override + public void registerHydratedState(Object[] hydratedState) { + this.hydratedState = hydratedState; + } + + @Override + public Object[] getHydratedState() { + return hydratedState; + } + + @Override + public void registerEntityInstance(Object entityInstance) { + this.entityInstance = entityInstance; + } + + @Override + public Object getEntityInstance() { + return entityInstance; + } }; identifierResolutionContextMap.put( entityReference, context ); } @@ -189,15 +271,55 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex return context; } - @Override - public Set getIdentifierResolutionContexts() { - return Collections.unmodifiableSet( - new HashSet( identifierResolutionContextMap.values() ) + private void registerNonExists(EntityFetch fetch) { + final EntityType fetchedType = fetch.getFetchedType(); + if ( ! fetchedType.isOneToOne() ) { + return; + } + + final EntityReferenceProcessingState fetchOwnerState = getOwnerProcessingState( fetch ); + if ( fetchOwnerState == null ) { + throw new IllegalStateException( "Could not locate fetch owner state" ); + } + + final EntityKey ownerEntityKey = fetchOwnerState.getEntityKey(); + if ( ownerEntityKey == null ) { + throw new IllegalStateException( "Could not locate fetch owner EntityKey" ); + } + + session.getPersistenceContext().addNullProperty( + ownerEntityKey, + fetchedType.getPropertyName() ); } @Override - public LoadQueryAliasResolutionContext getLoadQueryAliasResolutionContext() { + public EntityReferenceProcessingState getOwnerProcessingState(Fetch fetch) { + return getProcessingState( resolveFetchOwnerEntityReference( fetch ) ); + } + + private EntityReference resolveFetchOwnerEntityReference(Fetch fetch) { + final FetchOwner fetchOwner = fetch.getOwner(); + + if ( EntityReference.class.isInstance( fetchOwner ) ) { + return (EntityReference) fetchOwner; + } + else if ( CompositeFetch.class.isInstance( fetchOwner ) ) { + return resolveFetchOwnerEntityReference( (CompositeFetch) fetchOwner ); + } + + throw new IllegalStateException( + String.format( + "Cannot resolve FetchOwner [%s] of Fetch [%s (%s)] to an EntityReference", + fetchOwner, + fetch, + fetch.getPropertyPath() + ) + ); + } + + @Override + public AliasResolutionContext getAliasResolutionContext() { return aliasResolutionContext; } @@ -309,7 +431,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex checkVersion( resultSet, entityKeyContext.getEntityPersister(), - aliasResolutionContext.resolveEntityColumnAliases( entityKeyContext.getEntityReference() ), + aliasResolutionContext.resolveAliases( entityKeyContext.getEntityReference() ).getColumnAliases(), entityKey, existing ); @@ -324,14 +446,26 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex final String concreteEntityTypeName = getConcreteEntityTypeName( resultSet, entityKeyContext.getEntityPersister(), - aliasResolutionContext.resolveEntityColumnAliases( entityKeyContext.getEntityReference() ), + aliasResolutionContext.resolveAliases( entityKeyContext.getEntityReference() ).getColumnAliases(), entityKey ); - final Object entityInstance = getSession().instantiate( - concreteEntityTypeName, - entityKey.getIdentifier() - ); + final Object entityInstance; +// if ( suppliedOptionalEntityKey != null && entityKey.equals( suppliedOptionalEntityKey ) ) { +// // its the given optional object +// entityInstance = queryParameters.getOptionalObject(); +// } +// else { + // instantiate a new instance + entityInstance = session.instantiate( concreteEntityTypeName, entityKey.getIdentifier() ); +// } + + FetchStrategy fetchStrategy = null; + final EntityReference entityReference = entityKeyContext.getEntityReference(); + if ( EntityFetch.class.isInstance( entityReference ) ) { + final EntityFetch fetch = (EntityFetch) entityReference; + fetchStrategy = fetch.getFetchStrategy(); + } //need to hydrate it. @@ -350,15 +484,16 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex entityInstance, concreteEntityTypeName, entityKey, - aliasResolutionContext.resolveEntityColumnAliases( entityKeyContext.getEntityReference() ), + aliasResolutionContext.resolveAliases( entityKeyContext.getEntityReference() ).getColumnAliases(), acquiredLockMode, entityKeyContext.getEntityPersister(), + fetchStrategy, true, entityKeyContext.getEntityPersister().getEntityMetamodel().getEntityType() ); // materialize associations (and initialize the object) later - registerHydratedEntity( entityKeyContext.getEntityPersister(), entityKey, entityInstance ); + registerHydratedEntity( entityKeyContext.getEntityReference(), entityKey, entityInstance ); return entityInstance; } @@ -373,6 +508,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex EntityAliases entityAliases, LockMode acquiredLockMode, EntityPersister rootPersister, + FetchStrategy fetchStrategy, boolean eagerFetch, EntityType associationType) { @@ -400,7 +536,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex entityInstance, persister, acquiredLockMode, - !eagerFetch, + !forceFetchLazyAttributes, session ); @@ -417,7 +553,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex entityInstance, (Loadable) rootPersister, cols, - eagerFetch, + loadPlan.areLazyAttributesForceFetched(), session ); } @@ -469,7 +605,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex rowId, entityInstance, acquiredLockMode, - !eagerFetch, + !loadPlan.areLazyAttributesForceFetched(), session ); @@ -485,7 +621,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex null, null, rootCollectionReturn.getCollectionPersister(), - aliasResolutionContext.resolveCollectionColumnAliases( rootCollectionReturn ), + aliasResolutionContext.resolveAliases( rootCollectionReturn ).getCollectionColumnAliases(), resultSet, session ); @@ -499,7 +635,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex owner, collectionFetch.getCollectionPersister().getCollectionType().getKeyOfOwner( owner, session ), collectionFetch.getCollectionPersister(), - aliasResolutionContext.resolveCollectionColumnAliases( collectionFetch ), + aliasResolutionContext.resolveAliases( collectionFetch ).getCollectionColumnAliases(), resultSet, session ); @@ -583,11 +719,17 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex } @Override - public void registerHydratedEntity(EntityPersister persister, EntityKey entityKey, Object entityInstance) { + public void registerHydratedEntity(EntityReference entityReference, EntityKey entityKey, Object entityInstance) { if ( currentRowHydratedEntityRegistrationList == null ) { currentRowHydratedEntityRegistrationList = new ArrayList(); } - currentRowHydratedEntityRegistrationList.add( new HydratedEntityRegistration( persister, entityKey, entityInstance ) ); + currentRowHydratedEntityRegistrationList.add( + new HydratedEntityRegistration( + entityReference, + entityKey, + entityInstance + ) + ); } /** @@ -612,10 +754,10 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex subselectLoadableEntityKeyMap = new HashMap>(); } for ( HydratedEntityRegistration registration : currentRowHydratedEntityRegistrationList ) { - Set entityKeys = subselectLoadableEntityKeyMap.get( registration.persister ); + Set entityKeys = subselectLoadableEntityKeyMap.get( registration.entityReference.getEntityPersister() ); if ( entityKeys == null ) { entityKeys = new HashSet(); - subselectLoadableEntityKeyMap.put( registration.persister, entityKeys ); + subselectLoadableEntityKeyMap.put( registration.entityReference.getEntityPersister(), entityKeys ); } entityKeys.add( registration.key ); } @@ -623,6 +765,8 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex // release the currentRowHydratedEntityRegistrationList entries currentRowHydratedEntityRegistrationList.clear(); + + identifierResolutionContextMap.clear(); } /** @@ -753,7 +897,7 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex TwoPhaseLoad.postLoad( registration.instance, session, postLoadEvent ); if ( afterLoadActionList != null ) { for ( AfterLoadAction afterLoadAction : afterLoadActionList ) { - afterLoadAction.afterLoad( session, registration.instance, (Loadable) registration.persister ); + afterLoadAction.afterLoad( session, registration.instance, (Loadable) registration.entityReference.getEntityPersister() ); } } } @@ -790,12 +934,12 @@ public class ResultSetProcessingContextImpl implements ResultSetProcessingContex } private static class HydratedEntityRegistration { - private final EntityPersister persister; + private final EntityReference entityReference; private final EntityKey key; - private final Object instance; + private Object instance; - private HydratedEntityRegistration(EntityPersister persister, EntityKey key, Object instance) { - this.persister = persister; + private HydratedEntityRegistration(EntityReference entityReference, EntityKey key, Object instance) { + this.entityReference = entityReference; this.key = key; this.instance = instance; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessorHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorHelper.java similarity index 65% rename from hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessorHelper.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorHelper.java index 8401218694..06f5a003ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/ResultSetProcessorHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorHelper.java @@ -21,37 +21,57 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.internal; +package org.hibernate.loader.plan.exec.process.internal; import java.io.Serializable; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.loader.EntityAliases; -import org.hibernate.loader.plan.spi.EntityReference; -import org.hibernate.loader.plan.spi.Fetch; -import org.hibernate.loader.spi.NamedParameterContext; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CompositeType; -import org.hibernate.type.Type; /** * @author Steve Ebersole */ public class ResultSetProcessorHelper { + /** + * Singleton access + */ + public static final ResultSetProcessorHelper INSTANCE = new ResultSetProcessorHelper(); + public static EntityKey getOptionalObjectKey(QueryParameters queryParameters, SessionImplementor session) { final Object optionalObject = queryParameters.getOptionalObject(); final Serializable optionalId = queryParameters.getOptionalId(); final String optionalEntityName = queryParameters.getOptionalEntityName(); - if ( optionalObject != null && optionalEntityName != null ) { - return session.generateEntityKey( optionalId, session.getEntityPersister( optionalEntityName, optionalObject ) ); + return INSTANCE.interpretEntityKey( session, optionalEntityName, optionalId, optionalObject ); + } + + public EntityKey interpretEntityKey( + SessionImplementor session, + String optionalEntityName, + Serializable optionalId, + Object optionalObject) { + if ( optionalEntityName != null ) { + final EntityPersister entityPersister; + if ( optionalObject != null ) { + entityPersister = session.getEntityPersister( optionalEntityName, optionalObject ); + } + else { + entityPersister = session.getFactory().getEntityPersister( optionalEntityName ); + } + if ( entityPersister.isInstance( optionalId ) ) { + // embedded (non-encapsulated) composite identifier + final Serializable identifierState = ( (CompositeType) entityPersister.getIdentifierType() ).getPropertyValues( optionalId, session ); + return session.generateEntityKey( identifierState, entityPersister ); + } + else { + return session.generateEntityKey( optionalId, entityPersister ); + } } else { return null; @@ -74,4 +94,5 @@ public class ResultSetProcessorHelper { } return namedParameterLocMap; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java new file mode 100644 index 0000000000..60a77179be --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java @@ -0,0 +1,543 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.dialect.pagination.LimitHelper; +import org.hibernate.engine.FetchStyle; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.RowSelection; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.loader.plan.exec.process.spi.ReturnReader; +import org.hibernate.loader.plan.spi.CollectionFetch; +import org.hibernate.loader.plan.spi.CollectionReference; +import org.hibernate.loader.plan.spi.CollectionReturn; +import org.hibernate.loader.plan.spi.CompositeFetch; +import org.hibernate.loader.plan.spi.EntityFetch; +import org.hibernate.loader.plan.spi.EntityReference; +import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.loader.plan.spi.Fetch; +import org.hibernate.loader.plan.spi.FetchOwner; +import org.hibernate.loader.plan.spi.LoadPlan; +import org.hibernate.loader.plan.spi.ScalarReturn; +import org.hibernate.loader.plan.spi.visit.LoadPlanVisitationStrategyAdapter; +import org.hibernate.loader.plan.spi.visit.LoadPlanVisitor; +import org.hibernate.loader.plan.spi.Return; +import org.hibernate.loader.spi.AfterLoadAction; +import org.hibernate.loader.spi.LoadPlanAdvisor; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.process.spi.ScrollableResultSetProcessor; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessor; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.transform.ResultTransformer; + +/** + * @author Steve Ebersole + */ +public class ResultSetProcessorImpl implements ResultSetProcessor { + private static final Logger LOG = Logger.getLogger( ResultSetProcessorImpl.class ); + + private final LoadPlan baseLoadPlan; + private final RowReader rowReader; + + private final boolean shouldUseOptionalEntityInstance; + + private final boolean hadSubselectFetches; + + public ResultSetProcessorImpl( + LoadPlan loadPlan, + boolean shouldUseOptionalEntityInstance) { + this.baseLoadPlan = loadPlan; + this.rowReader = buildRowReader( loadPlan ); + this.shouldUseOptionalEntityInstance = shouldUseOptionalEntityInstance; + + LocalVisitationStrategy strategy = new LocalVisitationStrategy(); + LoadPlanVisitor.visit( loadPlan, strategy ); + this.hadSubselectFetches = strategy.hadSubselectFetches; + } + + private RowReader buildRowReader(LoadPlan loadPlan) { + switch ( loadPlan.getDisposition() ) { + case MIXED: { + return new MixedReturnRowReader( loadPlan ); + } + case ENTITY_LOADER: { + return new EntityLoaderRowReader( loadPlan ); + } + case COLLECTION_INITIALIZER: { + return new CollectionInitializerRowReader( loadPlan ); + } + default: { + throw new IllegalStateException( "Unrecognized LoadPlan Return dispostion : " + loadPlan.getDisposition() ); + } + } + } + + @Override + public ScrollableResultSetProcessor toOnDemandForm() { + // todo : implement + throw new NotYetImplementedException(); + } + + @Override + public List extractResults( + LoadPlanAdvisor loadPlanAdvisor, + ResultSet resultSet, + final SessionImplementor session, + QueryParameters queryParameters, + NamedParameterContext namedParameterContext, + AliasResolutionContext aliasResolutionContext, + boolean returnProxies, + boolean readOnly, + ResultTransformer forcedResultTransformer, + List afterLoadActionList) throws SQLException { + + final LoadPlan loadPlan = loadPlanAdvisor.advise( this.baseLoadPlan ); + if ( loadPlan == null ) { + throw new IllegalStateException( "LoadPlanAdvisor returned null" ); + } + + handlePotentiallyEmptyCollectionRootReturns( loadPlan, queryParameters.getCollectionKeys(), resultSet, session ); + + final int maxRows; + final RowSelection selection = queryParameters.getRowSelection(); + if ( LimitHelper.hasMaxRows( selection ) ) { + maxRows = selection.getMaxRows(); + LOG.tracef( "Limiting ResultSet processing to just %s rows", maxRows ); + } + else { + maxRows = Integer.MAX_VALUE; + } + + // There are times when the "optional entity information" on QueryParameters should be used and + // times when they should be ignored. Loader uses its isSingleRowLoader method to allow + // subclasses to override that. Collection initializers, batch loaders, e.g. override that + // it to be false. The 'shouldUseOptionalEntityInstance' setting is meant to fill that same role. + final boolean shouldUseOptionalEntityInstance = true; + + // Handles the "FETCH ALL PROPERTIES" directive in HQL + final boolean forceFetchLazyAttributes = false; + + final ResultSetProcessingContextImpl context = new ResultSetProcessingContextImpl( + resultSet, + session, + loadPlan, + readOnly, + shouldUseOptionalEntityInstance, + forceFetchLazyAttributes, + returnProxies, + queryParameters, + namedParameterContext, + aliasResolutionContext, + hadSubselectFetches + ); + + final List loadResults = new ArrayList(); + + LOG.trace( "Processing result set" ); + int count; + for ( count = 0; count < maxRows && resultSet.next(); count++ ) { + LOG.debugf( "Starting ResultSet row #%s", count ); + + Object logicalRow = rowReader.readRow( resultSet, context ); + + // todo : apply transformers here? + + loadResults.add( logicalRow ); + + context.finishUpRow(); + } + + LOG.tracev( "Done processing result set ({0} rows)", count ); + + context.finishUp( afterLoadActionList ); + + session.getPersistenceContext().initializeNonLazyCollections(); + + return loadResults; + } + + + private void handlePotentiallyEmptyCollectionRootReturns( + LoadPlan loadPlan, + Serializable[] collectionKeys, + ResultSet resultSet, + SessionImplementor session) { + if ( collectionKeys == null ) { + // this is not a collection initializer (and empty collections will be detected by looking for + // the owner's identifier in the result set) + return; + } + + // this is a collection initializer, so we must create a collection + // for each of the passed-in keys, to account for the possibility + // that the collection is empty and has no rows in the result set + // + // todo : move this inside CollectionReturn ? + CollectionPersister persister = ( (CollectionReturn) loadPlan.getReturns().get( 0 ) ).getCollectionPersister(); + for ( Serializable key : collectionKeys ) { + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Preparing collection intializer : %s", + MessageHelper.collectionInfoString( persister, key, session.getFactory() ) + ); + session.getPersistenceContext() + .getLoadContexts() + .getCollectionLoadContext( resultSet ) + .getLoadingCollection( persister, key ); + } + } + } + + + private class LocalVisitationStrategy extends LoadPlanVisitationStrategyAdapter { + private boolean hadSubselectFetches = false; + + @Override + public void startingEntityFetch(EntityFetch entityFetch) { + // only collections are currently supported for subselect fetching. + // hadSubselectFetches = hadSubselectFetches + // || entityFetch.getFetchStrategy().getStyle() == FetchStyle.SUBSELECT; + } + + @Override + public void startingCollectionFetch(CollectionFetch collectionFetch) { + hadSubselectFetches = hadSubselectFetches + || collectionFetch.getFetchStrategy().getStyle() == FetchStyle.SUBSELECT; + } + } + + private static interface RowReader { + Object readRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException; + } + + private static abstract class AbstractRowReader implements RowReader { + + @Override + public Object readRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException { + final List entityReferenceReaders = getEntityReferenceReaders(); + final List collectionReferenceReaders = getCollectionReferenceReaders(); + + final boolean hasEntityReferenceReaders = entityReferenceReaders != null && entityReferenceReaders.size() > 0; + final boolean hasCollectionReferenceReaders = collectionReferenceReaders != null && collectionReferenceReaders.size() > 0; + + if ( hasEntityReferenceReaders ) { + // 1) allow entity references to resolve identifiers (in 2 steps) + for ( EntityReferenceReader entityReferenceReader : entityReferenceReaders ) { + entityReferenceReader.hydrateIdentifier( resultSet, context ); + } + for ( EntityReferenceReader entityReferenceReader : entityReferenceReaders ) { + entityReferenceReader.resolveEntityKey( resultSet, context ); + } + + + // 2) allow entity references to resolve their hydrated state and entity instance + for ( EntityReferenceReader entityReferenceReader : entityReferenceReaders ) { + entityReferenceReader.hydrateEntityState( resultSet, context ); + } + } + + + // 3) read the logical row + + Object logicalRow = readLogicalRow( resultSet, context ); + + + // 4) allow entities and collection to read their elements + if ( hasEntityReferenceReaders ) { + for ( EntityReferenceReader entityReferenceReader : entityReferenceReaders ) { + entityReferenceReader.finishUpRow( resultSet, context ); + } + } + if ( hasCollectionReferenceReaders ) { + for ( CollectionReferenceReader collectionReferenceReader : collectionReferenceReaders ) { + collectionReferenceReader.finishUpRow( resultSet, context ); + } + } + + return logicalRow; + } + + protected abstract List getEntityReferenceReaders(); + protected abstract List getCollectionReferenceReaders(); + + protected abstract Object readLogicalRow(ResultSet resultSet, ResultSetProcessingContextImpl context) + throws SQLException; + + } + + private class MixedReturnRowReader extends AbstractRowReader implements RowReader { + private final List returnReaders; + private List entityReferenceReaders = new ArrayList(); + private List collectionReferenceReaders = new ArrayList(); + + private final int numberOfReturns; + + public MixedReturnRowReader(LoadPlan loadPlan) { + LoadPlanVisitor.visit( + loadPlan, + new LoadPlanVisitationStrategyAdapter() { + @Override + public void startingEntityFetch(EntityFetch entityFetch) { + entityReferenceReaders.add( new EntityReferenceReader( entityFetch ) ); + } + + @Override + public void startingCollectionFetch(CollectionFetch collectionFetch) { + collectionReferenceReaders.add( new CollectionReferenceReader( collectionFetch ) ); + } + } + ); + + final List readers = new ArrayList(); + + for ( Return rtn : loadPlan.getReturns() ) { + final ReturnReader returnReader = buildReturnReader( rtn ); + if ( EntityReferenceReader.class.isInstance( returnReader ) ) { + entityReferenceReaders.add( (EntityReferenceReader) returnReader ); + } + readers.add( returnReader ); + } + + this.returnReaders = readers; + this.numberOfReturns = readers.size(); + } + + @Override + protected List getEntityReferenceReaders() { + return entityReferenceReaders; + } + + @Override + protected List getCollectionReferenceReaders() { + return collectionReferenceReaders; + } + + @Override + protected Object readLogicalRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException { + Object[] logicalRow = new Object[ numberOfReturns ]; + int pos = 0; + for ( ReturnReader reader : returnReaders ) { + logicalRow[pos] = reader.read( resultSet, context ); + pos++; + } + return logicalRow; + } + } + + private static ReturnReader buildReturnReader(Return rtn) { + if ( ScalarReturn.class.isInstance( rtn ) ) { + return new ScalarReturnReader( (ScalarReturn) rtn ); + } + else if ( EntityReturn.class.isInstance( rtn ) ) { + return new EntityReturnReader( (EntityReturn) rtn ); + } + else if ( CollectionReturn.class.isInstance( rtn ) ) { + return new CollectionReturnReader( (CollectionReturn) rtn ); + } + else { + throw new IllegalStateException( "Unknown Return type : " + rtn ); + } + } + + private static interface EntityReferenceReaderListBuildingAccess { + public void add(EntityReferenceReader reader); + } + + private static interface CollectionReferenceReaderListBuildingAccess { + public void add(CollectionReferenceReader reader); + } + + + private class EntityLoaderRowReader extends AbstractRowReader implements RowReader { + private final EntityReturnReader returnReader; + private final List entityReferenceReaders = new ArrayList(); + private List collectionReferenceReaders = null; + + public EntityLoaderRowReader(LoadPlan loadPlan) { + final EntityReturn entityReturn = (EntityReturn) loadPlan.getReturns().get( 0 ); + this.returnReader = (EntityReturnReader) buildReturnReader( entityReturn ); + +// final EntityReferenceReaderListBuildingAccess entityReaders = new EntityReferenceReaderListBuildingAccess() { +// @Override +// public void add(EntityReferenceReader reader) { +// entityReferenceReaders.add( reader ); +// } +// }; +// +// final CollectionReferenceReaderListBuildingAccess collectionReaders = new CollectionReferenceReaderListBuildingAccess() { +// @Override +// public void add(CollectionReferenceReader reader) { +// if ( collectionReferenceReaders == null ) { +// collectionReferenceReaders = new ArrayList(); +// } +// collectionReferenceReaders.add( reader ); +// } +// }; +// +// buildFetchReaders( entityReaders, collectionReaders, entityReturn, returnReader ); + + LoadPlanVisitor.visit( + loadPlan, + new LoadPlanVisitationStrategyAdapter() { + @Override + public void startingEntityFetch(EntityFetch entityFetch) { + entityReferenceReaders.add( new EntityReferenceReader( entityFetch ) ); + } + + @Override + public void startingCollectionFetch(CollectionFetch collectionFetch) { + if ( collectionReferenceReaders == null ) { + collectionReferenceReaders = new ArrayList(); + } + collectionReferenceReaders.add( new CollectionReferenceReader( collectionFetch ) ); + } + } + ); + + entityReferenceReaders.add( returnReader ); + } + +// private void buildFetchReaders( +// EntityReferenceReaderListBuildingAccess entityReaders, +// CollectionReferenceReaderListBuildingAccess collectionReaders, +// FetchOwner fetchOwner, +// EntityReferenceReader entityReferenceReader) { +// for ( Fetch fetch : fetchOwner.getFetches() ) { +// if ( CollectionFetch.class.isInstance( fetch ) ) { +// final CollectionFetch collectionFetch = (CollectionFetch) fetch; +// buildFetchReaders( +// entityReaders, +// collectionReaders, +// collectionFetch.getIndexGraph(), +// null +// ); +// buildFetchReaders( +// entityReaders, +// collectionReaders, +// collectionFetch.getElementGraph(), +// null +// ); +// collectionReaders.add( new CollectionReferenceReader( collectionFetch ) ); +// } +// else if ( CompositeFetch.class.isInstance( fetch ) ) { +// buildFetchReaders( +// entityReaders, +// collectionReaders, +// (CompositeFetch) fetch, +// entityReferenceReader +// ); +// } +// else { +// final EntityFetch entityFetch = (EntityFetch) fetch; +// if ( entityFetch.getFetchedType().isOneToOne() ) { +// // entityReferenceReader should reference the owner still... +// if ( entityReferenceReader == null ) { +// throw new IllegalStateException( "Entity reader for one-to-one fetch owner not known" ); +// } +// final EntityReferenceReader fetchReader = new OneToOneFetchReader( +// entityFetch, +// entityReferenceReader.getEntityReference() +// ); +// } +// else { +// +// } +// } +// } +// //To change body of created methods use File | Settings | File Templates. +// } + + @Override + protected List getEntityReferenceReaders() { + return entityReferenceReaders; + } + + @Override + protected List getCollectionReferenceReaders() { + return collectionReferenceReaders; + } + + @Override + protected Object readLogicalRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException { + return returnReader.read( resultSet, context ); + } + } + + private class CollectionInitializerRowReader extends AbstractRowReader implements RowReader { + private final CollectionReturnReader returnReader; + + private List entityReferenceReaders = null; + private final List collectionReferenceReaders = new ArrayList(); + + public CollectionInitializerRowReader(LoadPlan loadPlan) { + returnReader = (CollectionReturnReader) buildReturnReader( loadPlan.getReturns().get( 0 ) ); + + LoadPlanVisitor.visit( + loadPlan, + new LoadPlanVisitationStrategyAdapter() { + @Override + public void startingEntityFetch(EntityFetch entityFetch) { + if ( entityReferenceReaders == null ) { + entityReferenceReaders = new ArrayList(); + } + entityReferenceReaders.add( new EntityReferenceReader( entityFetch ) ); + } + + @Override + public void startingCollectionFetch(CollectionFetch collectionFetch) { + collectionReferenceReaders.add( new CollectionReferenceReader( collectionFetch ) ); + } + } + ); + + collectionReferenceReaders.add( returnReader ); + } + + @Override + protected List getEntityReferenceReaders() { + return entityReferenceReaders; + } + + @Override + protected List getCollectionReferenceReaders() { + return collectionReferenceReaders; + } + + @Override + protected Object readLogicalRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException { + return returnReader.read( resultSet, context ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ScalarReturnReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ScalarReturnReader.java new file mode 100644 index 0000000000..e36347f0b0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ScalarReturnReader.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.internal; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ReturnReader; +import org.hibernate.loader.plan.spi.ScalarReturn; + +/** + * @author Steve Ebersole + */ +public class ScalarReturnReader implements ReturnReader { + private final ScalarReturn scalarReturn; + + public ScalarReturnReader(ScalarReturn scalarReturn) { + this.scalarReturn = scalarReturn; + } + + @Override + public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + return scalarReturn.getType().nullSafeGet( + resultSet, + context.getAliasResolutionContext().resolveScalarColumnAliases( scalarReturn ), + context.getSession(), + null + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/ScrollableResultSetProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ScrollableResultSetProcessorImpl.java similarity index 93% rename from hibernate-core/src/main/java/org/hibernate/loader/internal/ScrollableResultSetProcessorImpl.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ScrollableResultSetProcessorImpl.java index 5e2d06104e..309d8cde04 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/ScrollableResultSetProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ScrollableResultSetProcessorImpl.java @@ -21,13 +21,13 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.internal; +package org.hibernate.loader.plan.exec.process.internal; import java.sql.ResultSet; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.loader.spi.ScrollableResultSetProcessor; +import org.hibernate.loader.plan.exec.process.spi.ScrollableResultSetProcessor; /** * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/package-info.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/package-info.java new file mode 100644 index 0000000000..028443e4af --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/package-info.java @@ -0,0 +1,4 @@ +/** + * Defines support for processing ResultSet values as defined by a LoadPlan + */ +package org.hibernate.loader.plan.exec.process; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessingContext.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessingContext.java new file mode 100644 index 0000000000..43249d2ed8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessingContext.java @@ -0,0 +1,171 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.spi; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.LockMode; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.loader.EntityAliases; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.exec.spi.LockModeResolver; +import org.hibernate.loader.plan.spi.EntityReference; +import org.hibernate.loader.plan.spi.Fetch; +import org.hibernate.loader.plan.spi.LoadPlan; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.EntityType; + +/** + * @author Steve Ebersole + */ +public interface ResultSetProcessingContext extends LockModeResolver { + public SessionImplementor getSession(); + + public QueryParameters getQueryParameters(); + + public boolean shouldUseOptionalEntityInformation(); + + public boolean shouldReturnProxies(); + + public LoadPlan getLoadPlan(); + + /** + * Holds all pieces of information known about an entity reference in relation to each row as we process the + * result set. Caches these values and makes it easy for access while processing Fetches. + */ + public static interface EntityReferenceProcessingState { + /** + * The EntityReference for which this is collecting process state + * + * @return The EntityReference + */ + public EntityReference getEntityReference(); + + /** + * Register the fact that no identifier was found on attempt to hydrate it from ResultSet + */ + public void registerMissingIdentifier(); + + /** + * + * @return + */ + public boolean isMissingIdentifier(); + + /** + * Register the hydrated form (raw Type-read ResultSet values) of the entity's identifier for the row + * currently being processed. + * + * @param hydratedForm The entity identifier hydrated state + */ + public void registerIdentifierHydratedForm(Object hydratedForm); + + /** + * Obtain the hydrated form (the raw Type-read ResultSet values) of the entity's identifier + * + * @return The entity identifier hydrated state + */ + public Object getIdentifierHydratedForm(); + + /** + * Register the processed EntityKey for this Entity for the row currently being processed. + * + * @param entityKey The processed EntityKey for this EntityReference + */ + public void registerEntityKey(EntityKey entityKey); + + /** + * Obtain the registered EntityKey for this EntityReference for the row currently being processed. + * + * @return The registered EntityKey for this EntityReference + */ + public EntityKey getEntityKey(); + + public void registerHydratedState(Object[] hydratedState); + public Object[] getHydratedState(); + + // usually uninitialized at this point + public void registerEntityInstance(Object instance); + + // may be uninitialized + public Object getEntityInstance(); + + } + + public EntityReferenceProcessingState getProcessingState(EntityReference entityReference); + + /** + * Find the EntityReferenceProcessingState for the FetchOwner of the given Fetch. + * + * @param fetch The Fetch for which to find the EntityReferenceProcessingState of its FetchOwner. + * + * @return The FetchOwner's EntityReferenceProcessingState + */ + public EntityReferenceProcessingState getOwnerProcessingState(Fetch fetch); + + + public AliasResolutionContext getAliasResolutionContext(); + + public void registerHydratedEntity(EntityReference entityReference, EntityKey entityKey, Object entityInstance); + + public static interface EntityKeyResolutionContext { + public EntityPersister getEntityPersister(); + public LockMode getLockMode(); + public EntityReference getEntityReference(); + } + + public Object resolveEntityKey(EntityKey entityKey, EntityKeyResolutionContext entityKeyContext); + + + // should be able to get rid of the methods below here from the interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public void checkVersion( + ResultSet resultSet, + EntityPersister persister, + EntityAliases entityAliases, + EntityKey entityKey, + Object entityInstance) throws SQLException; + + public String getConcreteEntityTypeName( + ResultSet resultSet, + EntityPersister persister, + EntityAliases entityAliases, + EntityKey entityKey) throws SQLException; + + public void loadFromResultSet( + ResultSet resultSet, + Object entityInstance, + String concreteEntityTypeName, + EntityKey entityKey, + EntityAliases entityAliases, + LockMode acquiredLockMode, + EntityPersister persister, + FetchStrategy fetchStrategy, + boolean eagerFetch, + EntityType associationType) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/ResultSetProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java similarity index 89% rename from hibernate-core/src/main/java/org/hibernate/loader/spi/ResultSetProcessor.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java index df733dc16c..211d48d83a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/ResultSetProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ResultSetProcessor.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.spi; +package org.hibernate.loader.plan.exec.process.spi; import java.sql.ResultSet; import java.sql.SQLException; @@ -29,6 +29,10 @@ import java.util.List; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.spi.AfterLoadAction; +import org.hibernate.loader.spi.LoadPlanAdvisor; import org.hibernate.transform.ResultTransformer; /** @@ -66,7 +70,7 @@ public interface ResultSetProcessor { SessionImplementor session, QueryParameters queryParameters, NamedParameterContext namedParameterContext, - LoadQueryAliasResolutionContext aliasResolutionContext, + AliasResolutionContext aliasResolutionContext, boolean returnProxies, boolean readOnly, ResultTransformer forcedResultTransformer, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ReturnReader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ReturnReader.java new file mode 100644 index 0000000000..a674d8ebd6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ReturnReader.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.process.spi; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Handles reading results from a JDBC ResultSet relating to a single Return object. + * + * @author Steve Ebersole + */ +public interface ReturnReader { + /** + * Essentially performs the second phase of two-phase loading. + * + * @param resultSet The result set being processed + * @param context The context for the processing + * + * @return The read object + * + * @throws SQLException Indicates a problem access the JDBC result set + */ + public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/ScrollableResultSetProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java similarity index 98% rename from hibernate-core/src/main/java/org/hibernate/loader/spi/ScrollableResultSetProcessor.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java index 3892bba3f5..1d425e62ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/ScrollableResultSetProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/spi/ScrollableResultSetProcessor.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.spi; +package org.hibernate.loader.plan.exec.process.spi; import java.sql.ResultSet; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/EntityLoadQueryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/EntityLoadQueryBuilderImpl.java new file mode 100644 index 0000000000..5db0e113e6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/EntityLoadQueryBuilderImpl.java @@ -0,0 +1,213 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.query.internal; + +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.plan.exec.query.spi.EntityLoadQueryBuilder; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.loader.plan.spi.LoadPlan; +import org.hibernate.loader.plan.spi.Return; +import org.hibernate.persister.entity.OuterJoinLoadable; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.sql.ConditionFragment; +import org.hibernate.sql.DisjunctionFragment; +import org.hibernate.sql.InFragment; + +/** + * @author Steve Ebersole + */ +public class EntityLoadQueryBuilderImpl implements EntityLoadQueryBuilder { + /** + * Singleton access + */ + public static final EntityLoadQueryBuilderImpl INSTANCE = new EntityLoadQueryBuilderImpl(); + + @Override + public String generateSql( + LoadPlan loadPlan, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters, + AliasResolutionContext aliasResolutionContext) { + final EntityReturn rootReturn = extractRootReturn( loadPlan ); + + return generateSql( + ( (Queryable) rootReturn.getEntityPersister() ).getKeyColumnNames(), + rootReturn, + factory, + buildingParameters, + aliasResolutionContext + ); + } + + private static EntityReturn extractRootReturn(LoadPlan loadPlan) { + if ( loadPlan.getReturns().size() == 0 ) { + throw new IllegalStateException( "LoadPlan contained no root returns" ); + } + else if ( loadPlan.getReturns().size() > 1 ) { + throw new IllegalStateException( "LoadPlan contained more than one root returns" ); + } + + final Return rootReturn = loadPlan.getReturns().get( 0 ); + if ( !EntityReturn.class.isInstance( rootReturn ) ) { + throw new IllegalStateException( + String.format( + "Unexpected LoadPlan root return; expecting %s, but found %s", + EntityReturn.class.getName(), + rootReturn.getClass().getName() + ) + ); + } + + return (EntityReturn) rootReturn; + } + + @Override + public String generateSql( + String[] keyColumnNames, + LoadPlan loadPlan, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters, + AliasResolutionContext aliasResolutionContext) { + final EntityReturn rootReturn = extractRootReturn( loadPlan ); + + return generateSql( + keyColumnNames, + rootReturn, + factory, + buildingParameters, + aliasResolutionContext + ); + } + + protected String generateSql( + String[] keyColumnNames, + EntityReturn rootReturn, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters, + AliasResolutionContext aliasResolutionContext) { + final SelectStatementBuilder select = new SelectStatementBuilder( factory.getDialect() ); + + // apply root entity return specifics + applyRootReturnSpecifics( select, keyColumnNames, rootReturn, factory, buildingParameters, aliasResolutionContext ); + + LoadQueryBuilderHelper.applyJoinFetches( + select, + factory, + rootReturn, + buildingParameters, + aliasResolutionContext + ); + + return select.toStatementString(); + } + + protected void applyRootReturnSpecifics( + SelectStatementBuilder select, + String[] keyColumnNames, + EntityReturn rootReturn, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters, + AliasResolutionContext aliasResolutionContext) { + final String rootAlias = aliasResolutionContext.resolveAliases( rootReturn ).getTableAlias(); + final OuterJoinLoadable rootLoadable = (OuterJoinLoadable) rootReturn.getEntityPersister(); + final Queryable rootQueryable = (Queryable) rootReturn.getEntityPersister(); + + applyKeyRestriction( select, rootAlias, keyColumnNames, buildingParameters.getBatchSize() ); + select.appendRestrictions( + rootQueryable.filterFragment( + rootAlias, + buildingParameters.getQueryInfluencers().getEnabledFilters() + ) + ); + select.appendRestrictions( rootLoadable.whereJoinFragment( rootAlias, true, true ) ); + select.appendSelectClauseFragment( + rootLoadable.selectFragment( + rootAlias, + aliasResolutionContext.resolveAliases( rootReturn ).getColumnAliases().getSuffix() + ) + ); + + final String fromTableFragment; + if ( buildingParameters.getLockOptions() != null ) { + fromTableFragment = factory.getDialect().appendLockHint( + buildingParameters.getLockOptions(), + rootLoadable.fromTableFragment( rootAlias ) + ); + select.setLockOptions( buildingParameters.getLockOptions() ); + } + else if ( buildingParameters.getLockMode() != null ) { + fromTableFragment = factory.getDialect().appendLockHint( + buildingParameters.getLockMode(), + rootLoadable.fromTableFragment( rootAlias ) + ); + select.setLockMode( buildingParameters.getLockMode() ); + } + else { + fromTableFragment = rootLoadable.fromTableFragment( rootAlias ); + } + select.appendFromClauseFragment( fromTableFragment + rootLoadable.fromJoinFragment( rootAlias, true, true ) ); + } + + private void applyKeyRestriction(SelectStatementBuilder select, String alias, String[] keyColumnNames, int batchSize) { + if ( keyColumnNames.length==1 ) { + // NOT A COMPOSITE KEY + // for batching, use "foo in (?, ?, ?)" for batching + // for no batching, use "foo = ?" + // (that distinction is handled inside InFragment) + final InFragment in = new InFragment().setColumn( alias, keyColumnNames[0] ); + for ( int i = 0; i < batchSize; i++ ) { + in.addValue( "?" ); + } + select.appendRestrictions( in.toFragmentString() ); + } + else { + // A COMPOSITE KEY... + final ConditionFragment keyRestrictionBuilder = new ConditionFragment() + .setTableAlias( alias ) + .setCondition( keyColumnNames, "?" ); + final String keyRestrictionFragment = keyRestrictionBuilder.toFragmentString(); + + StringBuilder restrictions = new StringBuilder(); + if ( batchSize==1 ) { + // for no batching, use "foo = ? and bar = ?" + restrictions.append( keyRestrictionFragment ); + } + else { + // for batching, use "( (foo = ? and bar = ?) or (foo = ? and bar = ?) )" + restrictions.append( '(' ); + DisjunctionFragment df = new DisjunctionFragment(); + for ( int i=0; iRole + // where User is the FetchOwner and Role (User.roles) is the Fetch. We'd have: + // 1) the owner's table : user + final String ownerTableAlias = resolveLhsTableAlias( fetchOwner, fetch, aliasResolutionContext ); + // 2) the m-n table : user_role + final String collectionTableAlias = aliasResolutionContext.resolveAliases( fetch ).getCollectionTableAlias(); + // 3) the element table : role + final String elementTableAlias = aliasResolutionContext.resolveAliases( fetch ).getElementTableAlias(); + + { + // add join fragments from the owner table -> collection table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + final String filterFragment = ( (Joinable) fetch.getCollectionPersister() ).filterFragment( + collectionTableAlias, + buildingParameters.getQueryInfluencers().getEnabledFilters() + ); + + joinFragment.addJoin( + joinableCollection.getTableName(), + collectionTableAlias, + StringHelper.qualify( ownerTableAlias, extractJoinable( fetchOwner ).getKeyColumnNames() ), + queryableCollection.getKeyColumnNames(), + fetch.isNullable() ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN, + filterFragment + ); + joinFragment.addJoins( + joinableCollection.fromJoinFragment( collectionTableAlias, false, true ), + joinableCollection.whereJoinFragment( collectionTableAlias, false, true ) + ); + + // add select fragments from the collection table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + selectStatementBuilder.appendSelectClauseFragment( + joinableCollection.selectFragment( + (Joinable) queryableCollection.getElementPersister(), + ownerTableAlias, + collectionTableAlias, + aliasResolutionContext.resolveAliases( fetch ).getEntityElementColumnAliases().getSuffix(), + aliasResolutionContext.resolveAliases( fetch ).getCollectionColumnAliases().getSuffix(), + true + ) + ); + } + + { + // add join fragments from the collection table -> element entity table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + final String additionalJoinConditions = resolveAdditionalJoinCondition( + factory, + elementTableAlias, + fetchOwner, + fetch, + buildingParameters.getQueryInfluencers(), + aliasResolutionContext + ); + + final String manyToManyFilter = fetch.getCollectionPersister().getManyToManyFilterFragment( + collectionTableAlias, + buildingParameters.getQueryInfluencers().getEnabledFilters() + ); + + final String condition; + if ( "".equals( manyToManyFilter ) ) { + condition = additionalJoinConditions; + } + else if ( "".equals( additionalJoinConditions ) ) { + condition = manyToManyFilter; + } + else { + condition = additionalJoinConditions + " and " + manyToManyFilter; + } + + final OuterJoinLoadable elementPersister = (OuterJoinLoadable) queryableCollection.getElementPersister(); + + addJoins( + joinFragment, + elementPersister, +// JoinType.INNER_JOIN, + JoinType.LEFT_OUTER_JOIN, + elementTableAlias, + elementPersister.getIdentifierColumnNames(), + StringHelper.qualify( collectionTableAlias, queryableCollection.getElementColumnNames() ), + condition + ); + + // add select fragments from the element entity table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + final CollectionReferenceAliases aliases = aliasResolutionContext.resolveAliases( fetch ); + selectStatementBuilder.appendSelectClauseFragment( + elementPersister.selectFragment( + aliases.getElementTableAlias(), + aliases.getEntityElementColumnAliases().getSuffix() + ) + ); + } + + final String manyToManyOrdering = queryableCollection.getManyToManyOrderByString( collectionTableAlias ); + if ( StringHelper.isNotEmpty( manyToManyOrdering ) ) { + selectStatementBuilder.appendOrderByFragment( manyToManyOrdering ); + } + + final String ordering = queryableCollection.getSQLOrderByString( collectionTableAlias ); + if ( StringHelper.isNotEmpty( ordering ) ) { + selectStatementBuilder.appendOrderByFragment( ordering ); + } + } + else { + final String rhsTableAlias = aliasResolutionContext.resolveAliases( fetch ).getElementTableAlias(); + final String[] rhsColumnNames = JoinHelper.getRHSColumnNames( fetch.getFetchedType(), factory ); + + final String lhsTableAlias = resolveLhsTableAlias( fetchOwner, fetch, aliasResolutionContext ); + // todo : this is not exactly correct. it assumes the join refers to the LHS PK + final String[] aliasedLhsColumnNames = fetch.toSqlSelectFragments( lhsTableAlias ); + + final String on = resolveAdditionalJoinCondition( + factory, + rhsTableAlias, + fetchOwner, + fetch, + buildingParameters.getQueryInfluencers(), + aliasResolutionContext + ); + + addJoins( + joinFragment, + joinableCollection, + fetch.isNullable() ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN, + rhsTableAlias, + rhsColumnNames, + aliasedLhsColumnNames, + on + ); + + // select the "collection columns" + selectStatementBuilder.appendSelectClauseFragment( + queryableCollection.selectFragment( + rhsTableAlias, + aliasResolutionContext.resolveAliases( fetch ).getCollectionColumnAliases().getSuffix() + ) + ); + + if ( fetch.getCollectionPersister().isOneToMany() ) { + // if the collection elements are entities, select the entity columns as well + final CollectionReferenceAliases aliases = aliasResolutionContext.resolveAliases( fetch ); + final OuterJoinLoadable elementPersister = (OuterJoinLoadable) queryableCollection.getElementPersister(); + selectStatementBuilder.appendSelectClauseFragment( + elementPersister.selectFragment( + aliases.getElementTableAlias(), + aliases.getEntityElementColumnAliases().getSuffix() + ) + ); + } + + final String ordering = queryableCollection.getSQLOrderByString( rhsTableAlias ); + if ( StringHelper.isNotEmpty( ordering ) ) { + selectStatementBuilder.appendOrderByFragment( ordering ); + } + } + + } + + private static Joinable extractJoinable(FetchOwner fetchOwner) { + // this is used for collection fetches. At the end of the day, a fetched collection must be owned by + // an entity. Find that entity's persister and return it + if ( EntityReference.class.isInstance( fetchOwner ) ) { + return (Joinable) ( (EntityReference) fetchOwner ).getEntityPersister(); + } + else if ( CompositeFetch.class.isInstance( fetchOwner ) ) { + return (Joinable) locateCompositeFetchEntityReferenceSource( (CompositeFetch) fetchOwner ).getEntityPersister(); + } + else if ( EntityElementGraph.class.isInstance( fetchOwner ) ) { + return (Joinable) ( (EntityElementGraph) fetchOwner ).getEntityPersister(); + } + + throw new IllegalStateException( "Uncertain how to extract Joinable from given FetchOwner : " + fetchOwner ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java new file mode 100644 index 0000000000..de3c5aa8dc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java @@ -0,0 +1,232 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.query.internal; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.sql.SelectFragment; + +/** + * Largely a copy of the {@link org.hibernate.sql.Select} class, but changed up slightly to better meet needs + * of building a SQL SELECT statement from a LoadPlan + * + * @author Steve Ebersole + * @author Gavin King + */ +public class SelectStatementBuilder { + public final Dialect dialect; + + private StringBuilder selectClause = new StringBuilder(); + private StringBuilder fromClause = new StringBuilder(); +// private StringBuilder outerJoinsAfterFrom; + private String outerJoinsAfterFrom; + private StringBuilder whereClause; +// private StringBuilder outerJoinsAfterWhere; + private String outerJoinsAfterWhere; + private StringBuilder orderByClause; + private String comment; + private LockOptions lockOptions = new LockOptions(); + + private int guesstimatedBufferSize = 20; + + public SelectStatementBuilder(Dialect dialect) { + this.dialect = dialect; + } + + /** + * Appends a select clause fragment + * + * @param selection The selection fragment + */ + public void appendSelectClauseFragment(String selection) { + if ( this.selectClause.length() > 0 ) { + this.selectClause.append( ", " ); + this.guesstimatedBufferSize += 2; + } + this.selectClause.append( selection ); + this.guesstimatedBufferSize += selection.length(); + } + + public void appendSelectClauseFragment(SelectFragment selectFragment) { + appendSelectClauseFragment( selectFragment.toFragmentString().substring( 2 ) ); + } + + public void appendFromClauseFragment(String fragment) { + if ( this.fromClause.length() > 0 ) { + this.fromClause.append( ", " ); + this.guesstimatedBufferSize += 2; + } + this.fromClause.append( fragment ); + this.guesstimatedBufferSize += fragment.length(); + } + + public void appendFromClauseFragment(String tableName, String alias) { + appendFromClauseFragment( tableName + ' ' + alias ); + } + + public void appendRestrictions(String restrictions) { + final String cleaned = cleanRestrictions( restrictions ); + if ( StringHelper.isEmpty( cleaned ) ) { + return; + } + + this.guesstimatedBufferSize += cleaned.length(); + + if ( whereClause == null ) { + whereClause = new StringBuilder( cleaned ); + } + else { + whereClause.append( " and " ).append( cleaned ); + this.guesstimatedBufferSize += 5; + } + } + + private String cleanRestrictions(String restrictions) { + restrictions = restrictions.trim(); + if ( restrictions.startsWith( "and" ) ) { + restrictions = restrictions.substring( 4 ); + } + if ( restrictions.endsWith( "and" ) ) { + restrictions = restrictions.substring( 0, restrictions.length()-4 ); + } + + return restrictions; + } + +// public void appendOuterJoins(String outerJoinsAfterFrom, String outerJoinsAfterWhere) { +// appendOuterJoinsAfterFrom( outerJoinsAfterFrom ); +// appendOuterJoinsAfterWhere( outerJoinsAfterWhere ); +// } +// +// private void appendOuterJoinsAfterFrom(String outerJoinsAfterFrom) { +// if ( this.outerJoinsAfterFrom == null ) { +// this.outerJoinsAfterFrom = new StringBuilder( outerJoinsAfterFrom ); +// } +// else { +// this.outerJoinsAfterFrom.append( ' ' ).append( outerJoinsAfterFrom ); +// } +// } +// +// private void appendOuterJoinsAfterWhere(String outerJoinsAfterWhere) { +// final String cleaned = cleanRestrictions( outerJoinsAfterWhere ); +// +// if ( this.outerJoinsAfterWhere == null ) { +// this.outerJoinsAfterWhere = new StringBuilder( cleaned ); +// } +// else { +// this.outerJoinsAfterWhere.append( " and " ).append( cleaned ); +// this.guesstimatedBufferSize += 5; +// } +// +// this.guesstimatedBufferSize += cleaned.length(); +// } + + public void setOuterJoins(String outerJoinsAfterFrom, String outerJoinsAfterWhere) { + this.outerJoinsAfterFrom = outerJoinsAfterFrom; + + final String cleanRestrictions = cleanRestrictions( outerJoinsAfterWhere ); + this.outerJoinsAfterWhere = cleanRestrictions; + + this.guesstimatedBufferSize += outerJoinsAfterFrom.length() + cleanRestrictions.length(); + } + + public void appendOrderByFragment(String ordering) { + if ( this.orderByClause == null ) { + this.orderByClause = new StringBuilder(); + } + else { + this.orderByClause.append( ", " ); + this.guesstimatedBufferSize += 2; + } + this.orderByClause.append( ordering ); + } + + public void setComment(String comment) { + this.comment = comment; + this.guesstimatedBufferSize += comment.length(); + } + + public void setLockMode(LockMode lockMode) { + this.lockOptions.setLockMode( lockMode ); + } + + public void setLockOptions(LockOptions lockOptions) { + LockOptions.copy( lockOptions, this.lockOptions ); + } + + /** + * Construct an SQL SELECT statement from the given clauses + */ + public String toStatementString() { + final StringBuilder buf = new StringBuilder( guesstimatedBufferSize ); + + if ( StringHelper.isNotEmpty( comment ) ) { + buf.append( "/* " ).append( comment ).append( " */ " ); + } + + buf.append( "select " ) + .append( selectClause ) + .append( " from " ) + .append( fromClause ); + + if ( StringHelper.isNotEmpty( outerJoinsAfterFrom ) ) { + buf.append( outerJoinsAfterFrom ); + } + + if ( isNotEmpty( whereClause ) || isNotEmpty( outerJoinsAfterWhere ) ) { + buf.append( " where " ); + // the outerJoinsAfterWhere needs to come before where clause to properly + // handle dynamic filters + if ( StringHelper.isNotEmpty( outerJoinsAfterWhere ) ) { + buf.append( outerJoinsAfterWhere ); + if ( isNotEmpty( whereClause ) ) { + buf.append( " and " ); + } + } + if ( isNotEmpty( whereClause ) ) { + buf.append( whereClause ); + } + } + + if ( orderByClause != null ) { + buf.append( " order by " ).append( orderByClause ); + } + + if ( lockOptions.getLockMode() != LockMode.NONE ) { + buf.append( dialect.getForUpdateString( lockOptions ) ); + } + + return dialect.transformSelectString( buf.toString() ); + } + + private boolean isNotEmpty(String string) { + return StringHelper.isNotEmpty( string ); + } + + private boolean isNotEmpty(StringBuilder builder) { + return builder != null && builder.length() > 0; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/package-info.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/package-info.java new file mode 100644 index 0000000000..732ccb6070 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/package-info.java @@ -0,0 +1,4 @@ +/** + * Defines support for build a query (SQL string specifically for now) based on a LoadPlan. + */ +package org.hibernate.loader.plan.exec.query; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/EntityLoadQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/EntityLoadQueryBuilder.java new file mode 100644 index 0000000000..16a9a06a26 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/EntityLoadQueryBuilder.java @@ -0,0 +1,73 @@ +/* + * jDocBook, processing of DocBook sources + * + * Copyright (c) 2013, 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.plan.exec.query.spi; + +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.spi.LoadPlan; + +/** + * Contract for generating the query (currently the SQL string specifically) based on a LoadPlan with a + * single root EntityReturn + * + * @author Steve Ebersole + * @author Gail Badner + */ +public interface EntityLoadQueryBuilder { + /** + * Generates the query for the performing load. + * + * @param loadPlan The load + * @param factory The session factory. + * @param buildingParameters Parameters influencing the building of the query + * @param aliasResolutionContext The alias resolution context. + * + * @return the SQL string for performing the load + */ + String generateSql( + LoadPlan loadPlan, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters, + AliasResolutionContext aliasResolutionContext); + + /** + * Generates the query for the performing load, based on the specified key column(s). + * + * @param keyColumnNames The names of the key columns to use + * @param loadPlan The load + * @param factory The session factory. + * @param buildingParameters Parameters influencing the building of the query + * @param aliasResolutionContext The alias resolution context. + * + * @return the SQL string for performing the load + */ + String generateSql( + String[] keyColumnNames, + LoadPlan loadPlan, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters, + AliasResolutionContext aliasResolutionContext); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/NamedParameterContext.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/NamedParameterContext.java similarity index 96% rename from hibernate-core/src/main/java/org/hibernate/loader/spi/NamedParameterContext.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/NamedParameterContext.java index ed4412b42b..0054a35141 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/NamedParameterContext.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/NamedParameterContext.java @@ -21,7 +21,7 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.spi; +package org.hibernate.loader.plan.exec.query.spi; /** * The context for named parameters. diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/QueryBuildingParameters.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/QueryBuildingParameters.java new file mode 100644 index 0000000000..4ad8f88cd2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/spi/QueryBuildingParameters.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.query.spi; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; + +/** + * @author Steve Ebersole + */ +public interface QueryBuildingParameters { + public LoadQueryInfluencers getQueryInfluencers(); + public int getBatchSize(); + + // ultimately it would be better to have a way to resolve the LockMode for a given Return/Fetch... + public LockMode getLockMode(); + public LockOptions getLockOptions(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadQueryAliasResolutionContext.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/AliasResolutionContext.java similarity index 50% rename from hibernate-core/src/main/java/org/hibernate/loader/spi/LoadQueryAliasResolutionContext.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/AliasResolutionContext.java index 20672f94e3..175e46e8dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadQueryAliasResolutionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/AliasResolutionContext.java @@ -21,91 +21,71 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.spi; +package org.hibernate.loader.plan.exec.spi; import org.hibernate.loader.CollectionAliases; import org.hibernate.loader.EntityAliases; -import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CollectionReference; import org.hibernate.loader.plan.spi.EntityReference; import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.loader.plan.spi.Return; import org.hibernate.loader.plan.spi.ScalarReturn; +import org.hibernate.loader.spi.JoinableAssociation; /** * Provides aliases that are used by load queries and ResultSet processors. * * @author Gail Badner + * @author Steve Ebersole */ -public interface LoadQueryAliasResolutionContext { - +public interface AliasResolutionContext { /** - * Resolve the alias associated with the specified {@link EntityReturn}. + * Resolve the source alias (select-clause assigned alias) associated with the specified Return. The source + * alias is the alias associated with the Return in the source query. + *

+ * The concept of a source alias only has meaning in the case of queries (HQL, Criteria, etc). Not sure we + * really need to keep these here. One argument for keeping them is that I always thought it would be nice to + * base the SQL aliases on the source aliases. Keeping the source aliases here would allow us to do that as + * we are generating those SQL aliases internally. + *

+ * Should also consider pushing the source "from clause aliases" here if we keep pushing the select aliases * - * @param entityReturn - the {@link EntityReturn}. + * @param theReturn The Return to locate * * @return the alias associated with the specified {@link EntityReturn}. */ - public String resolveEntityReturnAlias(EntityReturn entityReturn); + public String getSourceAlias(Return theReturn); /** - * Resolve the alias associated with the specified {@link CollectionReturn}. + * Resolve the SQL column aliases associated with the specified {@link ScalarReturn}. * - * @param collectionReturn - the {@link CollectionReturn}. + * @param scalarReturn The {@link ScalarReturn} for which we want SQL column aliases * - * @return the alias associated with {@link CollectionReturn}. + * @return The SQL column aliases associated with {@link ScalarReturn}. */ - public String resolveCollectionReturnAlias(CollectionReturn collectionReturn); + public String[] resolveScalarColumnAliases(ScalarReturn scalarReturn); /** - * Resolve the aliases associated with the specified {@link ScalarReturn}. + * Resolve the alias information related to the given entity reference. * - * @param scalarReturn - the {@link ScalarReturn}. + * @param entityReference The entity reference for which to obtain alias info * - * @return the alias associated with {@link ScalarReturn}. + * @return The resolved alias info, */ - String[] resolveScalarReturnAliases(ScalarReturn scalarReturn); + public EntityReferenceAliases resolveAliases(EntityReference entityReference); /** - * Resolve the SQL table alias for the specified {@link EntityReference}. + * Resolve the alias information related to the given collection reference. * - * @param entityReference - the {@link EntityReference}. - * @return The SQL table alias for the specified {@link EntityReference}. + * @param collectionReference The collection reference for which to obtain alias info + * + * @return The resolved alias info, */ - String resolveEntityTableAlias(EntityReference entityReference); + public CollectionReferenceAliases resolveAliases(CollectionReference collectionReference); - /** - * Returns the description of the aliases in the JDBC ResultSet that identify values "belonging" to - * an entity. - * - * @param entityReference - the {@link EntityReference} for the entity. - * - * @return The ResultSet alias descriptor for the {@link EntityReference} - */ - EntityAliases resolveEntityColumnAliases(EntityReference entityReference); - /** - * Resolve the SQL table alias for the specified {@link CollectionReference}. - * - * @param collectionReference - the {@link CollectionReference}. - * @return The SQL table alias for the specified {@link CollectionReference}. - */ - String resolveCollectionTableAlias(CollectionReference collectionReference); - /** - * Returns the description of the aliases in the JDBC ResultSet that identify values "belonging" to - * the specified {@link CollectionReference}. - * - * @return The ResultSet alias descriptor for the {@link CollectionReference} - */ - CollectionAliases resolveCollectionColumnAliases(CollectionReference collectionReference); - /** - * If the elements of this collection are entities, this methods returns the JDBC ResultSet alias descriptions - * for that entity; {@code null} indicates a non-entity collection. - * - * @return The ResultSet alias descriptor for the collection's entity element, or {@code null} - */ - EntityAliases resolveCollectionElementColumnAliases(CollectionReference collectionReference); /** * Resolve the table alias on the right-hand-side of the specified association. diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/CollectionReferenceAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/CollectionReferenceAliases.java new file mode 100644 index 0000000000..611c7e45ad --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/CollectionReferenceAliases.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.spi; + +import org.hibernate.loader.CollectionAliases; +import org.hibernate.loader.EntityAliases; + +/** + * @author Steve Ebersole + */ +public interface CollectionReferenceAliases { + /** + * Obtain the table alias used for the collection table of the CollectionReference. + * + * @return The collection table alias. + */ + public String getCollectionTableAlias(); + + /** + * Obtain the alias of the table that contains the collection element values. + *

+ * Unlike in the legacy Loader case, CollectionReferences in the LoadPlan code refer to both the + * collection and the elements *always*. In Loader the elements were handled by EntityPersister associations + * entries for one-to-many and many-to-many. In LoadPlan we need to describe the collection table/columns + * as well as the entity element table/columns. For "basic collections" and one-to-many collections, the + * "element table" and the "collection table" are actually the same. For the many-to-many case this will be + * different and we need to track it separately. + * + * @return The element table alias. Only different from {@link #getCollectionTableAlias()} in the case of + * many-to-many. + */ + public String getElementTableAlias(); + + /** + * Obtain the aliases for the columns related to the collection structure such as the FK, index/key, or identifier + * (idbag). + * + * @return The collection column aliases. + */ + public CollectionAliases getCollectionColumnAliases(); + + /** + * Obtain the column aliases for the element values when the element of the collection is an entity. + * + * @return The column aliases for the entity element; {@code null} if the collection element is not an entity. + */ + public EntityAliases getEntityElementColumnAliases(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/EntityReferenceAliases.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/EntityReferenceAliases.java new file mode 100644 index 0000000000..e5828f9e78 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/EntityReferenceAliases.java @@ -0,0 +1,54 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.spi; + +import org.hibernate.loader.EntityAliases; + +/** + * Aggregates the alias/suffix information in relation to an {@link org.hibernate.loader.plan.spi.EntityReference} + * + * todo : add a contract (interface) that can be shared by entity and collection alias info objects as lhs/rhs of a join ? + * + * @author Steve Ebersole + */ +public interface EntityReferenceAliases { + /** + * Obtain the table alias used for referencing the table of the EntityReference. + *

+ * Note that this currently just returns the "root alias" whereas sometimes an entity reference covers + * multiple tables. todo : to help manage this, consider a solution like TableAliasRoot from the initial ANTLR re-work + * see http://anonsvn.jboss.org/repos/hibernate/core/branches/antlr3/src/main/java/org/hibernate/sql/ast/alias/TableAliasGenerator.java + * + * @return The (root) table alias for the described entity reference. + */ + public String getTableAlias(); + + /** + * Obtain the column aliases for the select fragment columns associated with the described entity reference. These + * are the column renames by which the values can be extracted from the SQL result set. + * + * @return The column aliases associated with the described entity reference. + */ + public EntityAliases getColumnAliases(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/LoadQueryDetails.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/LoadQueryDetails.java new file mode 100644 index 0000000000..a9447578c1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/LoadQueryDetails.java @@ -0,0 +1,111 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.spi; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessor; +import org.hibernate.loader.plan.exec.query.internal.EntityLoadQueryBuilderImpl; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; +import org.hibernate.loader.plan.spi.LoadPlan; + +/** + * Wraps a LoadPlan (for an entity load) and exposes details about the query and its execution. + * + * @author Steve Ebersole + */ +public class LoadQueryDetails { + private final SessionFactoryImplementor factory; + private final LoadPlan loadPlan; + + private final AliasResolutionContext aliasResolutionContext; + private final String sqlStatement; + private final ResultSetProcessor resultSetProcessor; + + /** + * Constructs a LoadQueryDetails object from the given inputs. + * + * + * @param uniqueKeyColumnNames + * @param loadPlan The load plan + * @param factory The SessionFactory + * @param buildingParameters And influencers that would affect the generated SQL (mostly we are concerned with those + * that add additional joins here) + * + * @return The LoadQueryDetails + */ + public static LoadQueryDetails makeForBatching( + String[] uniqueKeyColumnNames, + LoadPlan loadPlan, + SessionFactoryImplementor factory, + QueryBuildingParameters buildingParameters) { + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( factory ); + final ResultSetProcessor resultSetProcessor = new ResultSetProcessorImpl( loadPlan, false ); + final String sqlStatement = EntityLoadQueryBuilderImpl.INSTANCE.generateSql( + uniqueKeyColumnNames, + loadPlan, + factory, + buildingParameters, + aliasResolutionContext + ); + return new LoadQueryDetails( factory, loadPlan, aliasResolutionContext, resultSetProcessor, sqlStatement ); + } + + private LoadQueryDetails( + SessionFactoryImplementor factory, + LoadPlan loadPlan, + AliasResolutionContext aliasResolutionContext, + ResultSetProcessor resultSetProcessor, + String sqlStatement) { + this.factory = factory; + this.loadPlan = loadPlan; + this.aliasResolutionContext = aliasResolutionContext; + this.resultSetProcessor = resultSetProcessor; + this.sqlStatement = sqlStatement; + } + + public SessionFactoryImplementor getFactory() { + return factory; + } + + public LoadPlan getLoadPlan() { + return loadPlan; + } + + public AliasResolutionContext getAliasResolutionContext() { + return aliasResolutionContext; + } + + public String getSqlStatement() { + return sqlStatement; + } + + public ResultSetProcessor getResultSetProcessor() { + return resultSetProcessor; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/LockModeResolver.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/LockModeResolver.java new file mode 100644 index 0000000000..55397a2a16 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/spi/LockModeResolver.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.exec.spi; + +import org.hibernate.LockMode; +import org.hibernate.loader.plan.spi.EntityReference; + +/** + * @author Steve Ebersole + */ +public interface LockModeResolver { + public LockMode resolveLockMode(EntityReference entityReference); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java index 1cc9e9c40c..05f1318c2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanBuildingHelper.java @@ -25,12 +25,16 @@ package org.hibernate.loader.plan.internal; import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; +import org.hibernate.loader.plan.spi.AbstractFetchOwner; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CompositeFetch; import org.hibernate.loader.plan.spi.EntityFetch; import org.hibernate.loader.plan.spi.FetchOwner; import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; /** @@ -47,7 +51,7 @@ public class LoadPlanBuildingHelper { LockMode.NONE, // todo : for now fetchOwner, fetchStrategy, - attributeDefinition.getName() + attributeDefinition ); } @@ -56,12 +60,11 @@ public class LoadPlanBuildingHelper { AssociationAttributeDefinition attributeDefinition, FetchStrategy fetchStrategy, LoadPlanBuildingContext loadPlanBuildingContext) { - return new EntityFetch( loadPlanBuildingContext.getSessionFactory(), LockMode.NONE, // todo : for now fetchOwner, - attributeDefinition.getName(), + attributeDefinition, fetchStrategy ); } @@ -73,7 +76,21 @@ public class LoadPlanBuildingHelper { return new CompositeFetch( loadPlanBuildingContext.getSessionFactory(), fetchOwner, - attributeDefinition.getName() + attributeDefinition + ); + } + + public static AnyFetch buildAnyFetch( + FetchOwner fetchOwner, + AttributeDefinition attribute, + AnyMappingDefinition anyDefinition, + FetchStrategy fetchStrategy, LoadPlanBuildingContext loadPlanBuildingContext) { + return new AnyFetch( + loadPlanBuildingContext.getSessionFactory(), + fetchOwner, + attribute, + anyDefinition, + fetchStrategy ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanImpl.java index e2df73e13b..f2d190abad 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/LoadPlanImpl.java @@ -26,6 +26,7 @@ package org.hibernate.loader.plan.internal; import java.util.Collections; import java.util.List; +import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.EntityReturn; import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.loader.plan.spi.Return; @@ -36,29 +37,45 @@ import org.hibernate.loader.plan.spi.Return; * @author Steve Ebersole */ public class LoadPlanImpl implements LoadPlan { - private final boolean hasScalars; - private final List returns; + private final List returns; + private final Disposition disposition; + private final boolean areLazyAttributesForceFetched; - public LoadPlanImpl(boolean hasScalars, List returns) { - this.hasScalars = hasScalars; + protected LoadPlanImpl(List returns, Disposition disposition, boolean areLazyAttributesForceFetched) { this.returns = returns; + this.disposition = disposition; + this.areLazyAttributesForceFetched = areLazyAttributesForceFetched; } - public LoadPlanImpl(boolean hasScalars, Return rootReturn) { - this( hasScalars, Collections.singletonList( rootReturn ) ); + public LoadPlanImpl(EntityReturn rootReturn) { + this( Collections.singletonList( rootReturn ), Disposition.ENTITY_LOADER, false ); } - public LoadPlanImpl(EntityReturn entityReturn) { - this( false, entityReturn ); + public LoadPlanImpl(CollectionReturn rootReturn) { + this( Collections.singletonList( rootReturn ), Disposition.ENTITY_LOADER, false ); + } + + public LoadPlanImpl(List returns, boolean areLazyAttributesForceFetched) { + this( returns, Disposition.MIXED, areLazyAttributesForceFetched ); + } + + @Override + public List getReturns() { + return returns; + } + + @Override + public Disposition getDisposition() { + return disposition; + } + + @Override + public boolean areLazyAttributesForceFetched() { + return areLazyAttributesForceFetched; } @Override public boolean hasAnyScalarReturns() { - return hasScalars; - } - - @Override - public List getReturns() { - return returns; + return disposition == Disposition.MIXED; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/SingleRootReturnLoadPlanBuilderStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/SingleRootReturnLoadPlanBuilderStrategy.java index aebb39fce7..7ff3039e54 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/SingleRootReturnLoadPlanBuilderStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/internal/SingleRootReturnLoadPlanBuilderStrategy.java @@ -91,7 +91,15 @@ public class SingleRootReturnLoadPlanBuilderStrategy @Override public LoadPlan buildLoadPlan() { - return new LoadPlanImpl( false, rootReturn ); + if ( EntityReturn.class.isInstance( rootReturn ) ) { + return new LoadPlanImpl( (EntityReturn) rootReturn ); + } + else if ( CollectionReturn.class.isInstance( rootReturn ) ) { + return new LoadPlanImpl( (CollectionReturn) rootReturn ); + } + else { + throw new IllegalStateException( "Unexpected root Return type : " + rootReturn ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractCollectionReference.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractCollectionReference.java index ea1c61659d..3cf0665646 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractCollectionReference.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractCollectionReference.java @@ -27,6 +27,7 @@ import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.type.EntityType; import org.hibernate.type.Type; /** @@ -44,14 +45,15 @@ public abstract class AbstractCollectionReference extends AbstractPlanNode imple SessionFactoryImplementor sessionFactory, LockMode lockMode, CollectionPersister collectionPersister, - PropertyPath propertyPath) { + PropertyPath propertyPath, + EntityReference ownerEntityReference) { super( sessionFactory ); this.lockMode = lockMode; this.collectionPersister = collectionPersister; this.propertyPath = propertyPath; this.indexGraph = buildIndexGraph( getCollectionPersister() ); - this.elementGraph = buildElementGraph( getCollectionPersister() ); + this.elementGraph = buildElementGraph( getCollectionPersister(), ownerEntityReference ); } private FetchableCollectionIndex buildIndexGraph(CollectionPersister persister) { @@ -70,10 +72,30 @@ public abstract class AbstractCollectionReference extends AbstractPlanNode imple return null; } - private FetchableCollectionElement buildElementGraph(CollectionPersister persister) { + private FetchableCollectionElement buildElementGraph( + CollectionPersister persister, + EntityReference ownerEntityReference) { final Type type = persister.getElementType(); if ( type.isAssociationType() ) { if ( type.isEntityType() ) { + final EntityType elementEntityType = (EntityType) type; + + if ( ownerEntityReference != null ) { + // check for bi-directionality + final boolean sameType = elementEntityType.getAssociatedEntityName().equals( + ownerEntityReference.getEntityPersister().getEntityName() + ); + if ( sameType ) { + // todo : check for columns too... + + return new BidirectionalEntityElementGraph( + sessionFactory(), + this, + getPropertyPath(), + ownerEntityReference + ); + } + } return new EntityElementGraph( sessionFactory(), this, getPropertyPath() ); } } @@ -119,8 +141,22 @@ public abstract class AbstractCollectionReference extends AbstractPlanNode imple return elementGraph; } - @Override - public boolean hasEntityElements() { - return getCollectionPersister().isOneToMany() || getCollectionPersister().isManyToMany(); + + private class BidirectionalEntityElementGraph extends EntityElementGraph implements BidirectionalEntityFetch { + private final EntityReference targetEntityReference; + + private BidirectionalEntityElementGraph( + SessionFactoryImplementor sessionFactory, + CollectionReference collectionReference, + PropertyPath propertyPath, + EntityReference targetEntityReference) { + super( sessionFactory, collectionReference, propertyPath ); + this.targetEntityReference = targetEntityReference; + } + + @Override + public EntityReference getTargetEntityReference() { + return targetEntityReference; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwner.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwner.java index b078d46cc1..fcc51e13c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwner.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwner.java @@ -31,7 +31,9 @@ import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.plan.internal.LoadPlanBuildingHelper; import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.type.Type; @@ -100,25 +102,34 @@ public abstract class AbstractFetchOwner extends AbstractPlanNode implements Fet return fetches == null ? NO_FETCHES : fetches.toArray( new Fetch[ fetches.size() ] ); } - /** - * Abstract method returning the delegate for obtaining details about an owned fetch. - * @return the delegate - */ - protected abstract FetchOwnerDelegate getFetchOwnerDelegate(); - @Override public boolean isNullable(Fetch fetch) { - return getFetchOwnerDelegate().locateFetchMetadata( fetch ).isNullable(); + return fetch.isNullable(); } @Override public Type getType(Fetch fetch) { - return getFetchOwnerDelegate().locateFetchMetadata( fetch ).getType(); + return fetch.getFetchedType(); } @Override public String[] toSqlSelectFragments(Fetch fetch, String alias) { - return getFetchOwnerDelegate().locateFetchMetadata( fetch ).toSqlSelectFragments( alias ); + return fetch.toSqlSelectFragments( alias ); + } + + @Override + public AnyFetch buildAnyFetch( + AttributeDefinition attribute, + AnyMappingDefinition anyDefinition, + FetchStrategy fetchStrategy, + LoadPlanBuildingContext loadPlanBuildingContext) { + return LoadPlanBuildingHelper.buildAnyFetch( + this, + attribute, + anyDefinition, + fetchStrategy, + loadPlanBuildingContext + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractSingularAttributeFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractSingularAttributeFetch.java index e3d07dc3ab..2f589ddeb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractSingularAttributeFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractSingularAttributeFetch.java @@ -28,6 +28,8 @@ import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; +import org.hibernate.persister.walking.spi.AttributeDefinition; +import org.hibernate.type.Type; /** * Represents a singular attribute that is both a {@link FetchOwner} and a {@link Fetch}. @@ -37,7 +39,7 @@ import org.hibernate.loader.PropertyPath; */ public abstract class AbstractSingularAttributeFetch extends AbstractFetchOwner implements Fetch { private final FetchOwner owner; - private final String ownerProperty; + private final AttributeDefinition fetchedAttribute; private final FetchStrategy fetchStrategy; private final PropertyPath propertyPath; @@ -47,22 +49,22 @@ public abstract class AbstractSingularAttributeFetch extends AbstractFetchOwner * * @param factory - the session factory. * @param owner - the fetch owner for this fetch. - * @param ownerProperty - the owner's property referring to this fetch. + * @param fetchedAttribute - the attribute being fetched * @param fetchStrategy - the fetch strategy for this fetch. */ public AbstractSingularAttributeFetch( SessionFactoryImplementor factory, FetchOwner owner, - String ownerProperty, + AttributeDefinition fetchedAttribute, FetchStrategy fetchStrategy) { super( factory ); this.owner = owner; - this.ownerProperty = ownerProperty; + this.fetchedAttribute = fetchedAttribute; this.fetchStrategy = fetchStrategy; owner.addFetch( this ); - this.propertyPath = owner.getPropertyPath().append( ownerProperty ); + this.propertyPath = owner.getPropertyPath().append( fetchedAttribute.getName() ); } public AbstractSingularAttributeFetch( @@ -71,7 +73,7 @@ public abstract class AbstractSingularAttributeFetch extends AbstractFetchOwner FetchOwner fetchOwnerCopy) { super( original, copyContext ); this.owner = fetchOwnerCopy; - this.ownerProperty = original.ownerProperty; + this.fetchedAttribute = original.fetchedAttribute; this.fetchStrategy = original.fetchStrategy; this.propertyPath = original.propertyPath; } @@ -81,14 +83,19 @@ public abstract class AbstractSingularAttributeFetch extends AbstractFetchOwner return owner; } + public AttributeDefinition getFetchedAttribute() { + return fetchedAttribute; + } + @Override - public String getOwnerPropertyName() { - return ownerProperty; + public Type getFetchedType() { + return fetchedAttribute.getType(); } @Override public boolean isNullable() { - return owner.isNullable( this ); + return fetchedAttribute.isNullable(); +// return owner.isNullable( this ); } @Override @@ -102,10 +109,23 @@ public abstract class AbstractSingularAttributeFetch extends AbstractFetchOwner } @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { + public String getAdditionalJoinConditions() { + // only pertinent for HQL... + return null; + } + + @Override + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { if ( fetchStrategy.getStyle() == FetchStyle.JOIN ) { if ( this.fetchStrategy.getStyle() != FetchStyle.JOIN ) { - throw new HibernateException( "Cannot specify join fetch from owner that is a non-joined fetch" ); + + throw new HibernateException( + String.format( + "Cannot specify join fetch from owner [%s] that is a non-joined fetch : %s", + getPropertyPath().getFullPath(), + attributeDefinition.getName() + ) + ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AnyFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AnyFetch.java new file mode 100644 index 0000000000..a26cf822da --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AnyFetch.java @@ -0,0 +1,141 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.PropertyPath; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; +import org.hibernate.type.AnyType; + +/** + * @author Steve Ebersole + */ +public class AnyFetch extends AbstractPlanNode implements Fetch { + private final FetchOwner owner; + private final AttributeDefinition fetchedAttribute; + private final AnyMappingDefinition definition; + private final FetchStrategy fetchStrategy; + + private final PropertyPath propertyPath; + + public AnyFetch( + SessionFactoryImplementor sessionFactory, + FetchOwner owner, + AttributeDefinition ownerProperty, + AnyMappingDefinition definition, + FetchStrategy fetchStrategy) { + super( sessionFactory ); + + this.owner = owner; + this.fetchedAttribute = ownerProperty; + this.definition = definition; + this.fetchStrategy = fetchStrategy; + + this.propertyPath = owner.getPropertyPath().append( ownerProperty.getName() ); + + owner.addFetch( this ); + } + + /** + * Copy constructor. + * + * @param original The original fetch + * @param copyContext Access to contextual needs for the copy operation + */ + protected AnyFetch(AnyFetch original, CopyContext copyContext, FetchOwner fetchOwnerCopy) { + super( original ); + this.owner = fetchOwnerCopy; + this.fetchedAttribute = original.fetchedAttribute; + this.definition = original.definition; + this.fetchStrategy = original.fetchStrategy; + this.propertyPath = original.propertyPath; + } + + @Override + public FetchOwner getOwner() { + return owner; + } + + @Override + public AnyType getFetchedType() { + return (AnyType) fetchedAttribute.getType(); + } + + @Override + public boolean isNullable() { + return owner.isNullable( this ); + } + + @Override + public String[] toSqlSelectFragments(String alias) { + return owner.toSqlSelectFragments( this, alias ); + } + + @Override + public String getAdditionalJoinConditions() { + // only pertinent for HQL... + return null; + } + + @Override + public FetchStrategy getFetchStrategy() { + return fetchStrategy; + } + + @Override + public PropertyPath getPropertyPath() { + return propertyPath; + } + + @Override + public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + throw new NotYetImplementedException(); + } + + @Override + public Object resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { + throw new NotYetImplementedException(); + } + + @Override + public void read(ResultSet resultSet, ResultSetProcessingContext context, Object owner) throws SQLException { + throw new NotYetImplementedException(); + } + + @Override + public AnyFetch makeCopy(CopyContext copyContext, FetchOwner fetchOwnerCopy) { + copyContext.getReturnGraphVisitationStrategy().startingAnyFetch( this ); + final AnyFetch copy = new AnyFetch( this, copyContext, fetchOwnerCopy ); + copyContext.getReturnGraphVisitationStrategy().startingAnyFetch( this ); + return copy; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityFetch.java similarity index 61% rename from hibernate-core/src/main/java/org/hibernate/loader/spi/LoadQueryBuilder.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityFetch.java index a88548f587..8e00dbda3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/BidirectionalEntityFetch.java @@ -1,5 +1,5 @@ /* - * jDocBook, processing of DocBook sources + * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2013, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution @@ -21,24 +21,15 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.loader.spi; - -import org.hibernate.engine.spi.SessionFactoryImplementor; +package org.hibernate.loader.plan.spi; /** - * Builds a load query for generating SQL. + * Represents the circular side of a bi-directional entity fetch. Wraps a reference to an EntityReference + * as an EntityFetch. We can use the special type as a trigger in AliasResolutionContext, etc to lookup information + * based on the wrapped reference. * - * @author Gail Badner + * @author Steve Ebersole */ -public interface LoadQueryBuilder { - - /** - * Generates SQL for the performing the load. - * @param batchSize - the batch size. - * @param factory - the session factory. - * @param aliasResolutionContext - the alias resolution context. - * - * @return the SQL string for performing the load - */ - String generateSql(int batchSize, SessionFactoryImplementor factory, LoadQueryAliasResolutionContext aliasResolutionContext); +public interface BidirectionalEntityFetch { + public EntityReference getTargetEntityReference(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionFetch.java index 4f8816e7d1..9c9b0f2a52 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionFetch.java @@ -23,19 +23,34 @@ */ package org.hibernate.loader.plan.spi; +import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; +import org.jboss.logging.Logger; + import org.hibernate.LockMode; +import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.internal.CoreLogging; +import org.hibernate.loader.CollectionAliases; +import org.hibernate.loader.plan.exec.process.internal.Helper; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.persister.walking.spi.AttributeDefinition; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.type.CollectionType; /** * @author Steve Ebersole */ public class CollectionFetch extends AbstractCollectionReference implements Fetch { + private static final Logger log = CoreLogging.logger( CollectionFetch.class ); + private final FetchOwner fetchOwner; + private final AttributeDefinition fetchedAttribute; private final FetchStrategy fetchStrategy; public CollectionFetch( @@ -43,16 +58,16 @@ public class CollectionFetch extends AbstractCollectionReference implements Fetc LockMode lockMode, FetchOwner fetchOwner, FetchStrategy fetchStrategy, - String ownerProperty) { + AttributeDefinition fetchedAttribute) { super( sessionFactory, lockMode, - sessionFactory.getCollectionPersister( - fetchOwner.retrieveFetchSourcePersister().getEntityName() + '.' + ownerProperty - ), - fetchOwner.getPropertyPath().append( ownerProperty ) + sessionFactory.getCollectionPersister( ( (CollectionType) fetchedAttribute.getType() ).getRole() ), + fetchOwner.getPropertyPath().append( fetchedAttribute.getName() ), + (EntityReference) fetchOwner ); this.fetchOwner = fetchOwner; + this.fetchedAttribute = fetchedAttribute; this.fetchStrategy = fetchStrategy; fetchOwner.addFetch( this ); } @@ -60,6 +75,7 @@ public class CollectionFetch extends AbstractCollectionReference implements Fetc protected CollectionFetch(CollectionFetch original, CopyContext copyContext, FetchOwner fetchOwnerCopy) { super( original, copyContext ); this.fetchOwner = fetchOwnerCopy; + this.fetchedAttribute = original.fetchedAttribute; this.fetchStrategy = original.fetchStrategy; } @@ -69,8 +85,8 @@ public class CollectionFetch extends AbstractCollectionReference implements Fetc } @Override - public String getOwnerPropertyName() { - return getPropertyPath().getProperty(); + public CollectionType getFetchedType() { + return (CollectionType) fetchedAttribute.getType(); } @Override @@ -78,9 +94,15 @@ public class CollectionFetch extends AbstractCollectionReference implements Fetc return true; } + @Override + public String getAdditionalJoinConditions() { + // only pertinent for HQL... + return null; + } + @Override public String[] toSqlSelectFragments(String alias) { - return getOwner().toSqlSelectFragments( this, alias ); + return getOwner().toSqlSelectFragmentResolver().toSqlSelectFragments( alias, fetchedAttribute ); } @Override @@ -95,7 +117,84 @@ public class CollectionFetch extends AbstractCollectionReference implements Fetc @Override public Object resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - return null; //To change body of implemented methods use File | Settings | File Templates. + return null; + } + + @Override + public void read(ResultSet resultSet, ResultSetProcessingContext context, Object owner) throws SQLException { + final Serializable collectionRowKey = (Serializable) getCollectionPersister().readKey( + resultSet, + context.getAliasResolutionContext().resolveAliases( this ).getCollectionColumnAliases().getSuffixedKeyAliases(), + context.getSession() + ); + + final PersistenceContext persistenceContext = context.getSession().getPersistenceContext(); + + if ( collectionRowKey == null ) { + // we did not find a collection element in the result set, so we + // ensure that a collection is created with the owner's identifier, + // since what we have is an empty collection + final EntityKey ownerEntityKey = findOwnerEntityKey( context ); + if ( ownerEntityKey == null ) { + // should not happen + throw new IllegalStateException( + "Could not locate owner's EntityKey during attempt to read collection element fro JDBC row : " + + getPropertyPath().getFullPath() + ); + } + + if ( log.isDebugEnabled() ) { + log.debugf( + "Result set contains (possibly empty) collection: %s", + MessageHelper.collectionInfoString( + getCollectionPersister(), + ownerEntityKey, + context.getSession().getFactory() + ) + ); + } + + persistenceContext.getLoadContexts() + .getCollectionLoadContext( resultSet ) + .getLoadingCollection( getCollectionPersister(), ownerEntityKey ); + } + else { + // we found a collection element in the result set + if ( log.isDebugEnabled() ) { + log.debugf( + "Found row of collection: %s", + MessageHelper.collectionInfoString( + getCollectionPersister(), + collectionRowKey, + context.getSession().getFactory() + ) + ); + } + + PersistentCollection rowCollection = persistenceContext.getLoadContexts() + .getCollectionLoadContext( resultSet ) + .getLoadingCollection( getCollectionPersister(), collectionRowKey ); + + final CollectionAliases descriptor = context.getAliasResolutionContext().resolveAliases( this ).getCollectionColumnAliases(); + + if ( rowCollection != null ) { + final Object element = rowCollection.readFrom( resultSet, getCollectionPersister(), descriptor, owner ); + + if ( getElementGraph() != null ) { + for ( Fetch fetch : getElementGraph().getFetches() ) { + fetch.read( resultSet, context, element ); + } + } + } + } + } + + private EntityKey findOwnerEntityKey(ResultSetProcessingContext context) { + return context.getProcessingState( findOwnerEntityReference( getOwner() ) ).getEntityKey(); + } + + private EntityReference findOwnerEntityReference(FetchOwner owner) { + return Helper.INSTANCE.findOwnerEntityReference( owner ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReference.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReference.java index 83dec611cc..0311ff3b06 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReference.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReference.java @@ -53,6 +53,4 @@ public interface CollectionReference { public FetchOwner getElementGraph(); public PropertyPath getPropertyPath(); - - public boolean hasEntityElements(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReturn.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReturn.java index 933533ec80..4e709eb2ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CollectionReturn.java @@ -23,13 +23,9 @@ */ package org.hibernate.loader.plan.spi; -import java.sql.ResultSet; -import java.sql.SQLException; - import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; -import org.hibernate.loader.spi.ResultSetProcessingContext; /** * @author Steve Ebersole @@ -47,7 +43,10 @@ public class CollectionReturn extends AbstractCollectionReference implements Ret sessionFactory, lockMode, sessionFactory.getCollectionPersister( ownerEntityName + '.' + ownerProperty ), - new PropertyPath() // its a root + // its a root + new PropertyPath(), + // no owner + null ); this.ownerEntityName = ownerEntityName; this.ownerProperty = ownerProperty; @@ -77,21 +76,6 @@ public class CollectionReturn extends AbstractCollectionReference implements Ret return ownerProperty; } - @Override - public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - // todo : anything to do here? - } - - @Override - public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - // todo : anything to do here? - } - - @Override - public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - @Override public String toString() { return "CollectionReturn(" + getCollectionPersister().getRole() + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeBasedSqlSelectFragmentResolver.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeBasedSqlSelectFragmentResolver.java new file mode 100644 index 0000000000..5e8f6543bc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeBasedSqlSelectFragmentResolver.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +import java.util.Arrays; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.walking.spi.AttributeDefinition; +import org.hibernate.persister.walking.spi.WalkingException; +import org.hibernate.type.CompositeType; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class CompositeBasedSqlSelectFragmentResolver implements SqlSelectFragmentResolver { + protected static interface BaseSqlSelectFragmentResolver { + public String[] toSqlSelectFragments(String alias); + } + + public CompositeBasedSqlSelectFragmentResolver( + SessionFactoryImplementor sessionFactory, CompositeType compositeType, + BaseSqlSelectFragmentResolver baseResolver) { + this.sessionFactory = sessionFactory; + this.compositeType = compositeType; + this.baseResolver = baseResolver; + } + + private final SessionFactoryImplementor sessionFactory; + private final CompositeType compositeType; + private final BaseSqlSelectFragmentResolver baseResolver; + + @Override + public String[] toSqlSelectFragments(String alias, AttributeDefinition attributeDefinition) { + int subIndex = -1; + int selectFragmentRangeStart = 0; + int selectFragmentRangeEnd = -1; + + for ( int i = 0; i < compositeType.getPropertyNames().length; i++ ) { + final Type type = compositeType.getSubtypes()[i]; + final int typeColSpan = type.getColumnSpan( sessionFactory ); + if ( compositeType.getPropertyNames()[ i ].equals( attributeDefinition.getName() ) ) { + // fount it! + subIndex = i; + selectFragmentRangeEnd = selectFragmentRangeStart + typeColSpan; + break; + } + selectFragmentRangeStart += typeColSpan; + } + + if ( subIndex < 0 ) { + throw new WalkingException( + String.format( + "Owner property [%s] not found in composite properties [%s]", + attributeDefinition.getName(), + Arrays.asList( compositeType.getPropertyNames() ) + ) + ); + } + + return Arrays.copyOfRange( + baseResolver.toSqlSelectFragments( alias ), + selectFragmentRangeStart, + selectFragmentRangeEnd + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeElementGraph.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeElementGraph.java index c45c47ef2a..44ae6ddb35 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeElementGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeElementGraph.java @@ -9,6 +9,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.type.CompositeType; /** @@ -21,7 +22,7 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab private final CollectionReference collectionReference; private final PropertyPath propertyPath; private final CollectionPersister collectionPersister; - private final FetchOwnerDelegate fetchOwnerDelegate; + private final CompositeBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; /** * Constructs a {@link CompositeElementGraph}. @@ -39,15 +40,16 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab this.collectionReference = collectionReference; this.collectionPersister = collectionReference.getCollectionPersister(); this.propertyPath = collectionPath.append( "" ); - this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate( + this.sqlSelectFragmentResolver = new CompositeBasedSqlSelectFragmentResolver( sessionFactory, (CompositeType) collectionPersister.getElementType(), - new CompositeFetchOwnerDelegate.PropertyMappingDelegate() { + new CompositeBasedSqlSelectFragmentResolver.BaseSqlSelectFragmentResolver() { @Override public String[] toSqlSelectFragments(String alias) { - return ( (QueryableCollection) collectionPersister ).getElementColumnNames( alias ); + return ( (QueryableCollection) collectionPersister ).getElementColumnNames( alias ); } } + ); } @@ -56,7 +58,7 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab this.collectionReference = original.collectionReference; this.collectionPersister = original.collectionPersister; this.propertyPath = original.propertyPath; - this.fetchOwnerDelegate = original.fetchOwnerDelegate; + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; } @Override @@ -65,7 +67,7 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab } @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { } @Override @@ -83,11 +85,6 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab return new CompositeElementGraph( this, copyContext ); } - @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; - } - @Override public CollectionFetch buildCollectionFetch( AssociationAttributeDefinition attributeDefinition, @@ -95,4 +92,9 @@ public class CompositeElementGraph extends AbstractFetchOwner implements Fetchab LoadPlanBuildingContext loadPlanBuildingContext) { throw new HibernateException( "Collection composite element cannot define collections" ); } + + @Override + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java index 764cc4cd21..18f16accc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetch.java @@ -25,12 +25,19 @@ package org.hibernate.loader.plan.spi; import java.sql.ResultSet; import java.sql.SQLException; + +import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.internal.Helper; +import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.type.CompositeType; /** @@ -43,27 +50,28 @@ import org.hibernate.type.CompositeType; public class CompositeFetch extends AbstractSingularAttributeFetch { private static final FetchStrategy FETCH_PLAN = new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.JOIN ); - private final FetchOwnerDelegate delegate; + private final CompositeBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; /** * Constructs a {@link CompositeFetch} object. * * @param sessionFactory - the session factory. * @param owner - the fetch owner for this fetch. - * @param ownerProperty - the owner's property referring to this fetch. + * @param fetchedAttribute - the owner's property referring to this fetch. */ public CompositeFetch( SessionFactoryImplementor sessionFactory, final FetchOwner owner, - String ownerProperty) { - super( sessionFactory, owner, ownerProperty, FETCH_PLAN ); - this.delegate = new CompositeFetchOwnerDelegate( + final AttributeDefinition fetchedAttribute) { + super( sessionFactory, owner, fetchedAttribute, FETCH_PLAN ); + + this.sqlSelectFragmentResolver = new CompositeBasedSqlSelectFragmentResolver( sessionFactory, - (CompositeType) getOwner().getType( this ), - new CompositeFetchOwnerDelegate.PropertyMappingDelegate() { + (CompositeType) fetchedAttribute.getType(), + new CompositeBasedSqlSelectFragmentResolver.BaseSqlSelectFragmentResolver() { @Override public String[] toSqlSelectFragments(String alias) { - return owner.toSqlSelectFragments( CompositeFetch.this, alias ); + return owner.toSqlSelectFragmentResolver().toSqlSelectFragments( alias, fetchedAttribute ); } } ); @@ -71,12 +79,12 @@ public class CompositeFetch extends AbstractSingularAttributeFetch { public CompositeFetch(CompositeFetch original, CopyContext copyContext, FetchOwner fetchOwnerCopy) { super( original, copyContext, fetchOwnerCopy ); - this.delegate = original.getFetchOwnerDelegate(); + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; } @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return delegate; + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; } @Override @@ -86,12 +94,26 @@ public class CompositeFetch extends AbstractSingularAttributeFetch { @Override public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - //To change body of implemented methods use File | Settings | File Templates. + // anything to do? } @Override public Object resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - return null; //To change body of implemented methods use File | Settings | File Templates. + // anything to do? + return null; + } + + @Override + public void read(ResultSet resultSet, ResultSetProcessingContext context, Object owner) throws SQLException { + final EntityReference ownerEntityReference = Helper.INSTANCE.findOwnerEntityReference( this ); + final ResultSetProcessingContext.EntityReferenceProcessingState entityReferenceProcessingState = context.getProcessingState( + ownerEntityReference + ); + final EntityKey entityKey = entityReferenceProcessingState.getEntityKey(); + final Object entity = context.resolveEntityKey( entityKey, Helper.INSTANCE.findOwnerEntityReference( (FetchOwner) ownerEntityReference ) ); + for ( Fetch fetch : getFetches() ) { + fetch.read( resultSet, context, entity ); + } } @Override @@ -101,4 +123,18 @@ public class CompositeFetch extends AbstractSingularAttributeFetch { copyContext.getReturnGraphVisitationStrategy().finishingCompositeFetch( this ); return copy; } + + @Override + public CollectionFetch buildCollectionFetch( + AssociationAttributeDefinition attributeDefinition, + FetchStrategy fetchStrategy, + LoadPlanBuildingContext loadPlanBuildingContext) { + return new CollectionFetch( + loadPlanBuildingContext.getSessionFactory(), + LockMode.NONE, // todo : for now + this, + fetchStrategy, + attributeDefinition + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetchOwnerDelegate.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetchOwnerDelegate.java deleted file mode 100644 index 2785d75c28..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeFetchOwnerDelegate.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.plan.spi; - -import java.util.Arrays; - -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.persister.walking.spi.WalkingException; -import org.hibernate.type.CompositeType; -import org.hibernate.type.Type; - -/** - * A delegate for a composite fetch owner to obtain details about an - * owned sub-attribute fetch. - * - * @author Gail Badner - * @author Steve Ebersole - */ -public class CompositeFetchOwnerDelegate extends AbstractFetchOwnerDelegate implements FetchOwnerDelegate { - private final SessionFactoryImplementor sessionFactory; - private final CompositeType compositeType; - private final PropertyMappingDelegate propertyMappingDelegate; - - /** - * Constructs a CompositeFetchOwnerDelegate - * - * @param sessionFactory - the session factory. - * @param compositeType - the composite type. - * @param propertyMappingDelegate - delegate for handling property mapping - */ - public CompositeFetchOwnerDelegate( - SessionFactoryImplementor sessionFactory, - CompositeType compositeType, - PropertyMappingDelegate propertyMappingDelegate) { - this.sessionFactory = sessionFactory; - this.compositeType = compositeType; - this.propertyMappingDelegate = propertyMappingDelegate; - } - - public static interface PropertyMappingDelegate { - public String[] toSqlSelectFragments(String alias); - } - - @Override - protected FetchMetadata buildFetchMetadata(Fetch fetch) { - int subIndex = -1; - int selectFragmentRangeStart = 0; - int selectFragmentRangeEnd = -1; - - for ( int i = 0; i < compositeType.getPropertyNames().length; i++ ) { - final Type type = compositeType.getSubtypes()[i]; - final int typeColSpan = type.getColumnSpan( sessionFactory ); - if ( compositeType.getPropertyNames()[ i ].equals( fetch.getOwnerPropertyName() ) ) { - // fount it! - subIndex = i; - selectFragmentRangeEnd = selectFragmentRangeStart + typeColSpan; - break; - } - selectFragmentRangeStart += typeColSpan; - } - - if ( subIndex < 0 ) { - throw new WalkingException( - String.format( - "Owner property [%s] not found in composite properties [%s]", - fetch.getOwnerPropertyName(), - Arrays.asList( compositeType.getPropertyNames() ) - ) - ); - } - - return new FetchMetadataImpl( - compositeType, - subIndex, - propertyMappingDelegate, - selectFragmentRangeStart, - selectFragmentRangeEnd - ); - - // todo : we really need a PropertyMapping delegate which can encapsulate both the PropertyMapping and the path - } - - private static class FetchMetadataImpl implements FetchMetadata { - private final CompositeType compositeType; - private final int index; - private final PropertyMappingDelegate propertyMappingDelegate; - private final int selectFragmentRangeStart; - private final int selectFragmentRangeEnd; - - public FetchMetadataImpl( - CompositeType compositeType, - int index, - PropertyMappingDelegate propertyMappingDelegate, - int selectFragmentRangeStart, - int selectFragmentRangeEnd) { - this.compositeType = compositeType; - this.index = index; - this.propertyMappingDelegate = propertyMappingDelegate; - this.selectFragmentRangeStart = selectFragmentRangeStart; - this.selectFragmentRangeEnd = selectFragmentRangeEnd; - } - - @Override - public boolean isNullable() { - return compositeType.getPropertyNullability()[ index ]; - } - - @Override - public Type getType() { - return compositeType.getSubtypes()[ index ]; - } - - @Override - public String[] toSqlSelectFragments(String alias) { - return Arrays.copyOfRange( - propertyMappingDelegate.toSqlSelectFragments( alias ), - selectFragmentRangeStart, - selectFragmentRangeEnd - ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeIndexGraph.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeIndexGraph.java index b623368988..3cf59b0aec 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeIndexGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/CompositeIndexGraph.java @@ -9,6 +9,7 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.type.CompositeType; /** @@ -21,7 +22,7 @@ public class CompositeIndexGraph extends AbstractFetchOwner implements Fetchable private final CollectionReference collectionReference; private final PropertyPath propertyPath; private final CollectionPersister collectionPersister; - private final FetchOwnerDelegate fetchOwnerDelegate; + private final CompositeBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; /** * Constructs a {@link CompositeElementGraph}. @@ -38,10 +39,10 @@ public class CompositeIndexGraph extends AbstractFetchOwner implements Fetchable this.collectionReference = collectionReference; this.collectionPersister = collectionReference.getCollectionPersister(); this.propertyPath = collectionPath.append( "" ); - this.fetchOwnerDelegate = new CompositeFetchOwnerDelegate( + this.sqlSelectFragmentResolver = new CompositeBasedSqlSelectFragmentResolver( sessionFactory, (CompositeType) collectionPersister.getIndexType(), - new CompositeFetchOwnerDelegate.PropertyMappingDelegate() { + new CompositeBasedSqlSelectFragmentResolver.BaseSqlSelectFragmentResolver() { @Override public String[] toSqlSelectFragments(String alias) { return ( (QueryableCollection) collectionPersister ).getIndexColumnNames( alias ); @@ -55,11 +56,11 @@ public class CompositeIndexGraph extends AbstractFetchOwner implements Fetchable this.collectionReference = original.collectionReference; this.collectionPersister = original.collectionPersister; this.propertyPath = original.propertyPath; - this.fetchOwnerDelegate = original.fetchOwnerDelegate; + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; } @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { } @Override @@ -82,11 +83,6 @@ public class CompositeIndexGraph extends AbstractFetchOwner implements Fetchable return new CompositeIndexGraph( this, copyContext ); } - @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; - } - @Override public CollectionFetch buildCollectionFetch( AssociationAttributeDefinition attributeDefinition, @@ -95,4 +91,9 @@ public class CompositeIndexGraph extends AbstractFetchOwner implements Fetchable throw new HibernateException( "Composite index cannot define collections" ); } + @Override + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityElementGraph.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityElementGraph.java index b318efb667..840196f616 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityElementGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityElementGraph.java @@ -4,9 +4,14 @@ import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; +import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.type.AssociationType; +import org.hibernate.type.EntityType; /** * Represents the {@link FetchOwner} for a collection element that is @@ -20,7 +25,7 @@ public class EntityElementGraph extends AbstractFetchOwner implements FetchableC private final AssociationType elementType; private final EntityPersister elementPersister; private final PropertyPath propertyPath; - private final FetchOwnerDelegate fetchOwnerDelegate; + private final EntityPersisterBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; private IdentifierDescription identifierDescription; @@ -42,7 +47,7 @@ public class EntityElementGraph extends AbstractFetchOwner implements FetchableC this.elementType = (AssociationType) collectionPersister.getElementType(); this.elementPersister = (EntityPersister) this.elementType.getAssociatedJoinable( sessionFactory() ); this.propertyPath = collectionPath; - this.fetchOwnerDelegate = new EntityFetchOwnerDelegate( elementPersister ); + this.sqlSelectFragmentResolver = new EntityPersisterBasedSqlSelectFragmentResolver( (Queryable) elementPersister ); } public EntityElementGraph(EntityElementGraph original, CopyContext copyContext) { @@ -53,7 +58,7 @@ public class EntityElementGraph extends AbstractFetchOwner implements FetchableC this.elementType = original.elementType; this.elementPersister = original.elementPersister; this.propertyPath = original.propertyPath; - this.fetchOwnerDelegate = original.fetchOwnerDelegate; + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; } @Override @@ -77,7 +82,7 @@ public class EntityElementGraph extends AbstractFetchOwner implements FetchableC } @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { } @Override @@ -111,7 +116,65 @@ public class EntityElementGraph extends AbstractFetchOwner implements FetchableC } @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; + } + + @Override + public EntityFetch buildEntityFetch( + AssociationAttributeDefinition attributeDefinition, + FetchStrategy fetchStrategy, + LoadPlanBuildingContext loadPlanBuildingContext) { + final EntityType attributeType = (EntityType) attributeDefinition.getType(); + + final FetchOwner collectionOwner = CollectionFetch.class.isInstance( collectionReference ) + ? ( (CollectionFetch) collectionReference ).getOwner() + : null; + + if ( collectionOwner != null ) { + // check for bi-directionality + final boolean sameType = attributeType.getAssociatedEntityName().equals( + collectionOwner.retrieveFetchSourcePersister().getEntityName() + ); + + if ( sameType ) { + // todo : check for columns too... + + return new BidirectionalEntityElementGraphFetch( + sessionFactory(), + LockMode.READ, + this, + attributeDefinition, + fetchStrategy, + collectionOwner + ); + } + } + + return super.buildEntityFetch( + attributeDefinition, + fetchStrategy, + loadPlanBuildingContext + ); + } + + private class BidirectionalEntityElementGraphFetch extends EntityFetch implements BidirectionalEntityFetch { + private final FetchOwner collectionOwner; + + public BidirectionalEntityElementGraphFetch( + SessionFactoryImplementor sessionFactory, + LockMode lockMode, + FetchOwner owner, + AttributeDefinition fetchedAttribute, + FetchStrategy fetchStrategy, + FetchOwner collectionOwner) { + super( sessionFactory, lockMode, owner, fetchedAttribute, fetchStrategy ); + this.collectionOwner = collectionOwner; + } + + @Override + public EntityReference getTargetEntityReference() { + return (EntityReference) collectionOwner; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java index ed18f21465..770f93ff9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetch.java @@ -29,13 +29,17 @@ import java.sql.SQLException; import org.hibernate.LockMode; import org.hibernate.WrongClassException; import org.hibernate.engine.FetchStrategy; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.walking.spi.AttributeDefinition; +import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.type.EntityType; +import static org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.EntityReferenceProcessingState; + /** * Represents a {@link Fetch} for an entity association attribute as well as a * {@link FetchOwner} of the entity association sub-attribute fetches. @@ -43,35 +47,44 @@ import org.hibernate.type.EntityType; * @author Steve Ebersole */ public class EntityFetch extends AbstractSingularAttributeFetch implements EntityReference, Fetch { - private final EntityPersister persister; - private final LockMode lockMode; - private final FetchOwnerDelegate fetchOwnerDelegate; + private final EntityPersisterBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; private IdentifierDescription identifierDescription; + // todo : remove these + private final LockMode lockMode; + /** * Constructs an {@link EntityFetch} object. * * @param sessionFactory - the session factory. * @param lockMode - the lock mode. * @param owner - the fetch owner for this fetch. - * @param ownerProperty - the owner's property referring to this fetch. + * @param fetchedAttribute - the attribute being fetched * @param fetchStrategy - the fetch strategy for this fetch. */ public EntityFetch( SessionFactoryImplementor sessionFactory, LockMode lockMode, FetchOwner owner, - String ownerProperty, + AttributeDefinition fetchedAttribute, FetchStrategy fetchStrategy) { - super( sessionFactory, owner, ownerProperty, fetchStrategy ); + super( sessionFactory, owner, fetchedAttribute, fetchStrategy ); + + this.persister = sessionFactory.getEntityPersister( getFetchedType().getAssociatedEntityName() ); + if ( persister == null ) { + throw new WalkingException( + String.format( + "Unable to locate EntityPersister [%s] for fetch [%s]", + getFetchedType().getAssociatedEntityName(), + fetchedAttribute.getName() + ) + ); + } + this.sqlSelectFragmentResolver = new EntityPersisterBasedSqlSelectFragmentResolver( (Queryable) persister ); - this.persister = sessionFactory.getEntityPersister( - getEntityType().getAssociatedEntityName() - ); this.lockMode = lockMode; - this.fetchOwnerDelegate = new EntityFetchOwnerDelegate( persister ); } /** @@ -83,16 +96,19 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit protected EntityFetch(EntityFetch original, CopyContext copyContext, FetchOwner fetchOwnerCopy) { super( original, copyContext, fetchOwnerCopy ); this.persister = original.persister; + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; + this.lockMode = original.lockMode; - this.fetchOwnerDelegate = original.fetchOwnerDelegate; } - /** - * Returns the entity type for this fetch. - * @return the entity type for this fetch. - */ - public final EntityType getEntityType() { - return (EntityType) getOwner().getType( this ); + @Override + public EntityType getFetchedType() { + return (EntityType) super.getFetchedType(); + } + + @Override + public String[] toSqlSelectFragments(String alias) { + return getOwner().toSqlSelectFragmentResolver().toSqlSelectFragments( alias, getFetchedAttribute() ); } @Override @@ -105,6 +121,11 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit return persister; } + @Override + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; + } + @Override public IdentifierDescription getIdentifierDescription() { return identifierDescription; @@ -127,12 +148,6 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit @Override public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - EntityKey entityKey = context.getDictatedRootEntityKey(); - if ( entityKey != null ) { - context.getIdentifierResolutionContext( this ).registerEntityKey( entityKey ); - return; - } - identifierDescription.hydrate( resultSet, context ); for ( Fetch fetch : getFetches() ) { @@ -142,23 +157,25 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit @Override public EntityKey resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - final ResultSetProcessingContext.IdentifierResolutionContext identifierResolutionContext = context.getIdentifierResolutionContext( this ); - EntityKey entityKey = identifierResolutionContext.getEntityKey(); + final ResultSetProcessingContext.EntityReferenceProcessingState entityReferenceProcessingState = context.getProcessingState( + this + ); + EntityKey entityKey = entityReferenceProcessingState.getEntityKey(); if ( entityKey == null ) { entityKey = identifierDescription.resolve( resultSet, context ); if ( entityKey == null ) { // register the non-existence (though only for one-to-one associations) - if ( getEntityType().isOneToOne() ) { + if ( getFetchedType().isOneToOne() ) { // first, find our owner's entity-key... - final EntityKey ownersEntityKey = context.getIdentifierResolutionContext( (EntityReference) getOwner() ).getEntityKey(); + final EntityKey ownersEntityKey = context.getProcessingState( (EntityReference) getOwner() ).getEntityKey(); if ( ownersEntityKey != null ) { context.getSession().getPersistenceContext() - .addNullProperty( ownersEntityKey, getEntityType().getPropertyName() ); + .addNullProperty( ownersEntityKey, getFetchedType().getPropertyName() ); } } } - identifierResolutionContext.registerEntityKey( entityKey ); + entityReferenceProcessingState.registerEntityKey( entityKey ); for ( Fetch fetch : getFetches() ) { fetch.resolve( resultSet, context ); @@ -168,6 +185,19 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit return entityKey; } + @Override + public void read(ResultSet resultSet, ResultSetProcessingContext context, Object owner) throws SQLException { + final EntityReferenceProcessingState entityReferenceProcessingState = context.getProcessingState( this ); + final EntityKey entityKey = entityReferenceProcessingState.getEntityKey(); + if ( entityKey == null ) { + return; + } + final Object entity = context.resolveEntityKey( entityKey, this ); + for ( Fetch fetch : getFetches() ) { + fetch.read( resultSet, context, entity ); + } + } + /** * Resolve any fetches required to resolve the identifier as well * as the entity key for this fetch.. @@ -205,7 +235,7 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit context.checkVersion( resultSet, persister, - context.getLoadQueryAliasResolutionContext().resolveEntityColumnAliases( this ), + context.getAliasResolutionContext().resolveAliases( this ).getColumnAliases(), entityKey, existing ); @@ -218,7 +248,7 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit final String concreteEntityTypeName = context.getConcreteEntityTypeName( resultSet, persister, - context.getLoadQueryAliasResolutionContext().resolveEntityColumnAliases( this ), + context.getAliasResolutionContext().resolveAliases( this ).getColumnAliases(), entityKey ); @@ -239,15 +269,16 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit entityInstance, concreteEntityTypeName, entityKey, - context.getLoadQueryAliasResolutionContext().resolveEntityColumnAliases( this ), + context.getAliasResolutionContext().resolveAliases( this ).getColumnAliases(), acquiredLockMode, persister, - getFetchStrategy().getTiming() == FetchTiming.IMMEDIATE, - getEntityType() + getFetchStrategy(), + true, + getFetchedType() ); // materialize associations (and initialize the object) later - context.registerHydratedEntity( persister, entityKey, entityInstance ); + context.registerHydratedEntity( this, entityKey, entityInstance ); } return entityKey; @@ -265,9 +296,4 @@ public class EntityFetch extends AbstractSingularAttributeFetch implements Entit copyContext.getReturnGraphVisitationStrategy().finishingEntityFetch( this ); return copy; } - - @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetchOwnerDelegate.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetchOwnerDelegate.java deleted file mode 100644 index 0cbc6c509d..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityFetchOwnerDelegate.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.plan.spi; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Queryable; -import org.hibernate.persister.walking.spi.WalkingException; -import org.hibernate.type.CompositeType; -import org.hibernate.type.Type; - -/** - * A delegate for an entity fetch owner to obtain details about - * an owned attribute fetch. - * - * @author Gail Badner - * @author Steve Ebersole - */ -public class EntityFetchOwnerDelegate extends AbstractFetchOwnerDelegate implements FetchOwnerDelegate { - private final EntityPersister entityPersister; - - /** - * Constructs an {@link EntityFetchOwnerDelegate}. - * - * @param entityPersister - the entity persister. - */ - public EntityFetchOwnerDelegate(EntityPersister entityPersister) { - this.entityPersister = entityPersister; - } - - @Override - protected FetchMetadata buildFetchMetadata(Fetch fetch) { - final Integer propertyIndex = entityPersister.getEntityMetamodel().getPropertyIndexOrNull( - fetch.getOwnerPropertyName() - ); - if ( propertyIndex == null ) { - // possibly it is part of the identifier; but that's only possible if the identifier is composite - final Type idType = entityPersister.getIdentifierType(); - if ( CompositeType.class.isInstance( idType ) ) { - final CompositeType cidType = (CompositeType) idType; - if ( entityPersister.hasIdentifierProperty() ) { - // encapsulated composite id case; this *should have* been handled as part of building the fetch... - throw new WalkingException( - "Expecting different FetchOwnerDelegate type for encapsulated composite id case" - ); - } - else { - // non-encapsulated composite id case... - return new NonEncapsulatedIdentifierAttributeFetchMetadata( - entityPersister, - cidType, - fetch.getOwnerPropertyName() - ); - } - } - } - else { - return new NonIdentifierAttributeFetchMetadata( - entityPersister, - fetch.getOwnerPropertyName(), - propertyIndex.intValue() - ); - } - - throw new WalkingException( - "Could not locate metadata about given fetch [" + fetch + "] in its owning persister" - ); - } - - private class NonIdentifierAttributeFetchMetadata implements FetchMetadata { - private final EntityPersister entityPersister; - private final String attributeName; - private final int propertyIndex; - - private Type attributeType; - - public NonIdentifierAttributeFetchMetadata( - EntityPersister entityPersister, - String attributeName, - int propertyIndex) { - this.entityPersister = entityPersister; - this.attributeName = attributeName; - this.propertyIndex = propertyIndex; - } - - @Override - public boolean isNullable() { - return entityPersister.getPropertyNullability()[ propertyIndex ]; - } - - @Override - public Type getType() { - if ( attributeType == null ) { - attributeType = entityPersister.getPropertyTypes()[ propertyIndex ]; - } - return attributeType; - } - - @Override - public String[] toSqlSelectFragments(String alias) { -// final Type type = getType(); -// final OuterJoinLoadable outerJoinLoadable = (OuterJoinLoadable) entityPersister; - final Queryable queryable = (Queryable) entityPersister; - - return queryable.toColumns( alias, attributeName ); -// if ( type.isAssociationType() ) { -// return JoinHelper.getLHSColumnNames( -// (AssociationType) type, -// propertyIndex, -// outerJoinLoadable, -// outerJoinLoadable.getFactory() -// ); -// } -// else { -// return outerJoinLoadable.getPropertyColumnNames( propertyIndex ); -// } - } - } - - private class NonEncapsulatedIdentifierAttributeFetchMetadata implements FetchMetadata { - private final EntityPersister entityPersister; - private final String attributeName; - - // virtually final fields - private Type type; - private int selectFragmentRangeStart; - private int selectFragmentRangeEnd; - - - public NonEncapsulatedIdentifierAttributeFetchMetadata( - EntityPersister entityPersister, - CompositeType cidType, - String attributeName) { - this.entityPersister = entityPersister; - this.attributeName = attributeName; - - this.selectFragmentRangeStart = 0; - Type subType; - boolean foundIt = false; - for ( int i = 0; i < cidType.getPropertyNames().length; i++ ) { - subType = cidType.getSubtypes()[i]; - if ( cidType.getPropertyNames()[i].equals( attributeName ) ) { - // found it! - foundIt = true; - this.type = subType; - break; - } - selectFragmentRangeStart += subType.getColumnSpan( entityPersister.getFactory() ); - } - - if ( !foundIt ) { - throw new WalkingException( "Could not find " ); - } - - selectFragmentRangeEnd = selectFragmentRangeStart + type.getColumnSpan( entityPersister.getFactory() ); - } - - @Override - public boolean isNullable() { - return false; - } - - @Override - public Type getType() { - return type; - } - - @Override - public String[] toSqlSelectFragments(String alias) { - return Arrays.copyOfRange( - ( (Queryable) entityPersister ).toColumns( alias, attributeName ), - selectFragmentRangeStart, - selectFragmentRangeEnd - ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIndexGraph.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIndexGraph.java index df16559c81..2c7b0d2170 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIndexGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityIndexGraph.java @@ -29,6 +29,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.type.AssociationType; /** @@ -42,7 +44,7 @@ public class EntityIndexGraph extends AbstractFetchOwner implements FetchableCol private final AssociationType indexType; private final EntityPersister indexPersister; private final PropertyPath propertyPath; - private final FetchOwnerDelegate fetchOwnerDelegate; + private final EntityPersisterBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; private IdentifierDescription identifierDescription; @@ -63,7 +65,7 @@ public class EntityIndexGraph extends AbstractFetchOwner implements FetchableCol this.indexType = (AssociationType) collectionPersister.getIndexType(); this.indexPersister = (EntityPersister) this.indexType.getAssociatedJoinable( sessionFactory() ); this.propertyPath = collectionPath.append( "" ); // todo : do we want the part? - this.fetchOwnerDelegate = new EntityFetchOwnerDelegate( indexPersister ); + this.sqlSelectFragmentResolver = new EntityPersisterBasedSqlSelectFragmentResolver( (Queryable) indexPersister ); } public EntityIndexGraph(EntityIndexGraph original, CopyContext copyContext) { @@ -73,7 +75,7 @@ public class EntityIndexGraph extends AbstractFetchOwner implements FetchableCol this.indexType = original.indexType; this.indexPersister = original.indexPersister; this.propertyPath = original.propertyPath; - this.fetchOwnerDelegate = original.fetchOwnerDelegate; + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; } /** @@ -100,7 +102,7 @@ public class EntityIndexGraph extends AbstractFetchOwner implements FetchableCol } @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { } @Override @@ -129,7 +131,7 @@ public class EntityIndexGraph extends AbstractFetchOwner implements FetchableCol } @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwnerDelegate.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityPersisterBasedSqlSelectFragmentResolver.java similarity index 59% rename from hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwnerDelegate.java rename to hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityPersisterBasedSqlSelectFragmentResolver.java index 673ce80268..efdec15647 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/AbstractFetchOwnerDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityPersisterBasedSqlSelectFragmentResolver.java @@ -23,29 +23,22 @@ */ package org.hibernate.loader.plan.spi; -import java.util.HashMap; -import java.util.Map; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.walking.spi.AttributeDefinition; /** - * Base implementation of FetchOwnerDelegate providing caching of FetchMetadata. - * * @author Steve Ebersole */ -public abstract class AbstractFetchOwnerDelegate implements FetchOwnerDelegate { - private Map fetchMetadataMap; +public class EntityPersisterBasedSqlSelectFragmentResolver implements SqlSelectFragmentResolver { + private final Queryable entityPersister; - @Override - public FetchMetadata locateFetchMetadata(Fetch fetch) { - FetchMetadata metadata = fetchMetadataMap == null ? null : fetchMetadataMap.get( fetch.getOwnerPropertyName() ); - if ( metadata == null ) { - if ( fetchMetadataMap == null ) { - fetchMetadataMap = new HashMap(); - } - metadata = buildFetchMetadata( fetch ); - fetchMetadataMap.put( fetch.getOwnerPropertyName(), metadata ); - } - return metadata; + public EntityPersisterBasedSqlSelectFragmentResolver(Queryable entityPersister) { + this.entityPersister = entityPersister; + } + + @Override + public String[] toSqlSelectFragments(String alias, AttributeDefinition attributeDefinition) { + return entityPersister.toColumns( alias, attributeDefinition.getName() ); } - protected abstract FetchMetadata buildFetchMetadata(Fetch fetch); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReference.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReference.java index 3f646cc9a7..6c2c0f46e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReference.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReference.java @@ -24,7 +24,7 @@ package org.hibernate.loader.plan.spi; import org.hibernate.LockMode; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; import org.hibernate.persister.entity.EntityPersister; /** diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java index db6321aa7d..d1feabac14 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/EntityReturn.java @@ -23,19 +23,13 @@ */ package org.hibernate.loader.plan.spi; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.hibernate.AssertionFailure; import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; -import org.hibernate.loader.spi.ResultSetProcessingContext; import org.hibernate.persister.entity.EntityPersister; - -import static org.hibernate.loader.spi.ResultSetProcessingContext.IdentifierResolutionContext; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.walking.spi.AttributeDefinition; /** * Represents an entity return value in the query results. Not the same @@ -46,17 +40,14 @@ import static org.hibernate.loader.spi.ResultSetProcessingContext.IdentifierReso * @author Steve Ebersole */ public class EntityReturn extends AbstractFetchOwner implements Return, EntityReference, CopyableReturn { - private final EntityPersister persister; + private final EntityPersisterBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; + private IdentifierDescription identifierDescription; - private final PropertyPath propertyPath = new PropertyPath(); // it's a root + private final PropertyPath propertyPath; private final LockMode lockMode; - private final FetchOwnerDelegate fetchOwnerDelegate; - - private IdentifierDescription identifierDescription; - /** * Construct an {@link EntityReturn}. * @@ -70,15 +61,19 @@ public class EntityReturn extends AbstractFetchOwner implements Return, EntityRe String entityName) { super( sessionFactory ); this.persister = sessionFactory.getEntityPersister( entityName ); + this.propertyPath = new PropertyPath( entityName ); + this.sqlSelectFragmentResolver = new EntityPersisterBasedSqlSelectFragmentResolver( (Queryable) persister ); + this.lockMode = lockMode; - this.fetchOwnerDelegate = new EntityFetchOwnerDelegate( persister ); } protected EntityReturn(EntityReturn original, CopyContext copyContext) { super( original, copyContext ); this.persister = original.persister; + this.propertyPath = original.propertyPath; + this.sqlSelectFragmentResolver = original.sqlSelectFragmentResolver; + this.lockMode = original.lockMode; - this.fetchOwnerDelegate = original.fetchOwnerDelegate; } @Override @@ -102,7 +97,7 @@ public class EntityReturn extends AbstractFetchOwner implements Return, EntityRe } @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { } @Override @@ -115,67 +110,6 @@ public class EntityReturn extends AbstractFetchOwner implements Return, EntityRe return propertyPath; } - @Override - public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - EntityKey entityKey = getEntityKeyFromContext( context ); - if ( entityKey != null ) { - context.getIdentifierResolutionContext( this ).registerEntityKey( entityKey ); - return; - } - - identifierDescription.hydrate( resultSet, context ); - - for ( Fetch fetch : getFetches() ) { - fetch.hydrate( resultSet, context ); - } - } - - private EntityKey getEntityKeyFromContext(ResultSetProcessingContext context) { - if ( context.getDictatedRootEntityKey() != null ) { - return context.getDictatedRootEntityKey(); - } - else if ( context.getQueryParameters().getOptionalId() != null ) { - return context.getSession().generateEntityKey( - context.getQueryParameters().getOptionalId(), - getEntityPersister() - ); - } - return null; - } - - @Override - public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - final IdentifierResolutionContext identifierResolutionContext = context.getIdentifierResolutionContext( this ); - EntityKey entityKey = identifierResolutionContext.getEntityKey(); - if ( entityKey != null ) { - return; - } - - entityKey = identifierDescription.resolve( resultSet, context ); - identifierResolutionContext.registerEntityKey( entityKey ); - - for ( Fetch fetch : getFetches() ) { - fetch.resolve( resultSet, context ); - } - } - - @Override - public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - Object objectForThisEntityReturn = null; - for ( IdentifierResolutionContext identifierResolutionContext : context.getIdentifierResolutionContexts() ) { - final EntityReference entityReference = identifierResolutionContext.getEntityReference(); - final EntityKey entityKey = identifierResolutionContext.getEntityKey(); - if ( entityKey == null ) { - throw new AssertionFailure( "Could not locate resolved EntityKey"); - } - final Object object = context.resolveEntityKey( entityKey, entityReference ); - if ( this == entityReference ) { - objectForThisEntityReturn = object; - } - } - return objectForThisEntityReturn; - } - @Override public void injectIdentifierDescription(IdentifierDescription identifierDescription) { this.identifierDescription = identifierDescription; @@ -192,7 +126,7 @@ public class EntityReturn extends AbstractFetchOwner implements Return, EntityRe } @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Fetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Fetch.java index 017a78e1e8..240c3c505c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Fetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Fetch.java @@ -28,7 +28,8 @@ import java.sql.SQLException; import org.hibernate.engine.FetchStrategy; import org.hibernate.loader.PropertyPath; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.type.Type; /** * Contract for associations that are being fetched. @@ -46,25 +47,13 @@ public interface Fetch extends CopyableFetch { public FetchOwner getOwner(); /** - * Obtain the name of the property, relative to the owner, being fetched. + * Get the property path to this fetch * - * @return The fetched property name. + * @return The property path */ - public String getOwnerPropertyName(); + public PropertyPath getPropertyPath(); - /** - * Is this fetch nullable? - * - * @return true, if this fetch is nullable; false, otherwise. - */ - public boolean isNullable(); - - /** - * Generates the SQL select fragments for this fetch. A select fragment is the column and formula references. - * - * @return the select fragments - */ - public String[] toSqlSelectFragments(String alias); + public Type getFetchedType(); /** * Gets the fetch strategy for this fetch. @@ -74,16 +63,27 @@ public interface Fetch extends CopyableFetch { public FetchStrategy getFetchStrategy(); /** - * Get the property path to this fetch + * Is this fetch nullable? * - * @return The property path + * @return true, if this fetch is nullable; false, otherwise. */ - public PropertyPath getPropertyPath(); + public boolean isNullable(); + + public String getAdditionalJoinConditions(); + + /** + * Generates the SQL select fragments for this fetch. A select fragment is the column and formula references. + * + * @return the select fragments + */ + public String[] toSqlSelectFragments(String alias); public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; public Object resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; + public void read(ResultSet resultSet, ResultSetProcessingContext context, Object owner) throws SQLException; + @Override public Fetch makeCopy(CopyContext copyContext, FetchOwner fetchOwnerCopy); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwner.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwner.java index 4d0f77fbea..963f4cb2e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwner.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwner.java @@ -25,9 +25,13 @@ package org.hibernate.loader.plan.spi; import org.hibernate.engine.FetchStrategy; import org.hibernate.loader.PropertyPath; +import org.hibernate.loader.plan.spi.build.AbstractLoadPlanBuilderStrategy; import org.hibernate.loader.plan.spi.build.LoadPlanBuildingContext; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.PropertyMapping; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; +import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.type.Type; @@ -89,9 +93,10 @@ public interface FetchOwner { /** * Is the asserted plan valid from this owner to a fetch? * - * @param fetchStrategy The pla to validate + * @param fetchStrategy The type of fetch to validate + * @param attributeDefinition The attribute to be fetched */ - public void validateFetchPlan(FetchStrategy fetchStrategy); + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition); /** * Retrieve the EntityPersister that is the base for any property references in the fetches it owns. @@ -121,5 +126,11 @@ public interface FetchOwner { CompositionDefinition attributeDefinition, LoadPlanBuildingContext loadPlanBuildingContext); + public AnyFetch buildAnyFetch( + AttributeDefinition attribute, + AnyMappingDefinition anyDefinition, + FetchStrategy fetchStrategy, + LoadPlanBuildingContext loadPlanBuildingContext); + public SqlSelectFragmentResolver toSqlSelectFragmentResolver(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwnerDelegate.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwnerDelegate.java deleted file mode 100644 index 350169dab4..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/FetchOwnerDelegate.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.plan.spi; - -import org.hibernate.type.Type; - -/** - * This interface provides a delegate for a fetch owner to obtain details about an owned fetch. - * - * @author Gail Badner - */ -public interface FetchOwnerDelegate { - public static interface FetchMetadata { - /** - * Is the fetch nullable? - * - * @return true, if the fetch is nullable; false, otherwise. - */ - public boolean isNullable(); - - /** - * Returns the type of the fetched attribute - * - * @return the type of the fetched attribute. - */ - public Type getType(); - - /** - * Generates the SQL select fragments for the specified fetch. A select fragment is the column and formula - * references. - * - * @param alias The table alias to apply to the fragments (used to qualify column references) - * - * @return the select fragments - */ - public String[] toSqlSelectFragments(String alias); - } - - /** - * Locate the metadata for the specified Fetch. Allows easier caching of the resolved information. - * - * @param fetch The fetch for which to locate metadata - * - * @return The metadata; never {@code null}, rather an exception is thrown if the information for the fetch cannot - * be located. - */ - public FetchMetadata locateFetchMetadata(Fetch fetch); -} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/IdentifierDescription.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/IdentifierDescription.java index 62d8371196..2b676bcaeb 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/IdentifierDescription.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/IdentifierDescription.java @@ -27,7 +27,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import org.hibernate.engine.spi.EntityKey; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; +import org.hibernate.persister.spi.HydratedCompoundValueHandler; /** * @author Steve Ebersole @@ -38,4 +39,6 @@ public interface IdentifierDescription { public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; public EntityKey resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; + + HydratedCompoundValueHandler getHydratedStateHandler(Fetch fetch); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/KeyManyToOneBidirectionalEntityFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/KeyManyToOneBidirectionalEntityFetch.java new file mode 100644 index 0000000000..73bbf9e361 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/KeyManyToOneBidirectionalEntityFetch.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +import org.hibernate.LockMode; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.walking.spi.AttributeDefinition; + +/** + * We can use the special type as a trigger in AliasResolutionContext, etc to lookup information based on + * the wrapped reference. E.g. + * + * @author Steve Ebersole + */ +public class KeyManyToOneBidirectionalEntityFetch extends EntityFetch implements BidirectionalEntityFetch { + private final EntityReference targetEntityReference; + + public KeyManyToOneBidirectionalEntityFetch( + SessionFactoryImplementor sessionFactory, + LockMode lockMode, + FetchOwner owner, + AttributeDefinition fetchedAttribute, + EntityReference targetEntityReference, + FetchStrategy fetchStrategy) { + super( sessionFactory, lockMode, owner, fetchedAttribute, fetchStrategy ); + this.targetEntityReference = targetEntityReference; + } + + public KeyManyToOneBidirectionalEntityFetch( + KeyManyToOneBidirectionalEntityFetch original, + CopyContext copyContext, + FetchOwner fetchOwnerCopy) { + super( original, copyContext, fetchOwnerCopy ); + this.targetEntityReference = original.targetEntityReference; + } + + public EntityReference getTargetEntityReference() { + return targetEntityReference; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/LoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/LoadPlan.java index 0173b39714..fe5f3c89fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/LoadPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/LoadPlan.java @@ -46,6 +46,26 @@ import java.util.List; * @author Steve Ebersole */ public interface LoadPlan { + public List getReturns(); + + public Disposition getDisposition(); + + /** + * Does this load plan indicate that lazy attributes are to be force fetched? + *

+ * Here we are talking about laziness in regards to the legacy bytecode enhancement which adds support for + * partial selects of an entity's state (e.g., skip loading a lob initially, wait until/if it is needed) + *

+ * This one would effect the SQL that needs to get generated as well as how the result set would be read. + * Therefore we make this part of the LoadPlan contract. + *

+ * NOTE that currently this is only relevant for HQL loaders when the HQL has specified the {@code FETCH ALL PROPERTIES} + * key-phrase. In all other cases, this returns false. + + * @return Whether or not to + */ + public boolean areLazyAttributesForceFetched(); + /** * Convenient form of checking {@link #getReturns()} for scalar root returns. * @@ -53,7 +73,26 @@ public interface LoadPlan { */ public boolean hasAnyScalarReturns(); - public List getReturns(); + /** + * Enumerated possibilities for describing the disposition of this LoadPlan. + */ + public static enum Disposition { + /** + * This is an "entity loader" load plan, which describes a plan for loading one or more entity instances of + * the same entity type. There is a single return, which will be of type {@link EntityReturn} + */ + ENTITY_LOADER, + /** + * This is a "collection initializer" load plan, which describes a plan for loading one or more entity instances of + * the same collection type. There is a single return, which will be of type {@link CollectionReturn} + */ + COLLECTION_INITIALIZER, + /** + * We have a mixed load plan, which will have one or more returns of {@link EntityReturn} and {@link ScalarReturn} + * (NOT {@link CollectionReturn}). + */ + MIXED + } // todo : would also like to see "call back" style access for handling "subsequent actions" such as: // 1) follow-on locking diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Return.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Return.java index 19c700f57c..30bb6f97fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Return.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/Return.java @@ -23,11 +23,6 @@ */ package org.hibernate.loader.plan.spi; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.hibernate.loader.spi.ResultSetProcessingContext; - /** * Represents a return value in the query results. Not the same as a result (column) in the JDBC ResultSet! *

@@ -40,28 +35,4 @@ import org.hibernate.loader.spi.ResultSetProcessingContext; * @author Steve Ebersole */ public interface Return { - public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; - - /** - * Effectively performs first phase of two-phase loading. For scalar results first/second phase is one. For - * entities, first phase is to resolve identifiers; second phase is to resolve the entity instances. - * - * @param resultSet The result set being processed - * @param context The context for the processing - * - * @throws SQLException Indicates a problem access the JDBC result set - */ - public void resolve(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; - - /** - * Essentially performs the second phase of two-phase loading. - * - * @param resultSet The result set being processed - * @param context The context for the processing - * - * @return The read object - * - * @throws SQLException Indicates a problem access the JDBC result set - */ - public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/ScalarReturn.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/ScalarReturn.java index 39fb4f01ae..12dfcaf0f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/ScalarReturn.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/ScalarReturn.java @@ -23,16 +23,15 @@ */ package org.hibernate.loader.plan.spi; -import java.sql.ResultSet; -import java.sql.SQLException; - import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.loader.spi.ResultSetProcessingContext; import org.hibernate.type.Type; /** * Represent a simple scalar return within a query result. Generally this would be values of basic (String, Integer, * etc) or composite types. + *

+ * todo : we should link the Returns back to their "source" + * aka the entity/collection/etc that defines the qualifier used to qualify this Return's columns * * @author Steve Ebersole */ @@ -47,23 +46,4 @@ public class ScalarReturn extends AbstractPlanNode implements Return { public Type getType() { return type; } - - @Override - public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) { - // nothing to do - } - - @Override - public void resolve(ResultSet resultSet, ResultSetProcessingContext context) { - // nothing to do - } - - @Override - public Object read(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - return type.nullSafeGet( - resultSet, - context.getLoadQueryAliasResolutionContext().resolveScalarReturnAliases( this ), - context.getSession(), - null ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/SourceQualifiable.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/SourceQualifiable.java new file mode 100644 index 0000000000..70368e8136 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/SourceQualifiable.java @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +/** + * Marker interface for LoadPlan nodes that can be qualifiable in the source queries. + * + * @author Steve Ebersole + */ +public class SourceQualifiable { +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/SqlSelectFragmentResolver.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/SqlSelectFragmentResolver.java new file mode 100644 index 0000000000..e84f30252e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/SqlSelectFragmentResolver.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +import org.hibernate.persister.walking.spi.AttributeDefinition; + +/** + * Delegate for resolving the "SQL select fragments" pertaining to an attribute. + *

+ * Most implementations will delegate to the {@link org.hibernate.persister.entity.PropertyMapping} portion + * of the EntityPersister contract via the "optional" {@link org.hibernate.persister.entity.Queryable} contract. + * + * @author Steve Ebersole + * + * @see org.hibernate.persister.entity.PropertyMapping + */ +public interface SqlSelectFragmentResolver { + public String[] toSqlSelectFragments(String alias, AttributeDefinition attributeDefinition); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java index c95900e719..110da5e802 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/AbstractLoadPlanBuilderStrategy.java @@ -37,31 +37,34 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.FetchStrategy; -import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.FetchStyle; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.StringHelper; import org.hibernate.loader.PropertyPath; import org.hibernate.loader.plan.spi.AbstractFetchOwner; -import org.hibernate.loader.plan.spi.AbstractFetchOwnerDelegate; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReference; import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CompositeElementGraph; import org.hibernate.loader.plan.spi.CompositeFetch; -import org.hibernate.loader.plan.spi.CompositeFetchOwnerDelegate; import org.hibernate.loader.plan.spi.EntityFetch; +import org.hibernate.loader.plan.spi.EntityPersisterBasedSqlSelectFragmentResolver; import org.hibernate.loader.plan.spi.EntityReference; import org.hibernate.loader.plan.spi.EntityReturn; import org.hibernate.loader.plan.spi.Fetch; import org.hibernate.loader.plan.spi.FetchOwner; -import org.hibernate.loader.plan.spi.FetchOwnerDelegate; import org.hibernate.loader.plan.spi.IdentifierDescription; +import org.hibernate.loader.plan.spi.KeyManyToOneBidirectionalEntityFetch; import org.hibernate.loader.plan.spi.Return; -import org.hibernate.loader.spi.ResultSetProcessingContext; +import org.hibernate.loader.plan.spi.SqlSelectFragmentResolver; +import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.Loadable; +import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.spi.HydratedCompoundValueHandler; +import org.hibernate.persister.walking.internal.FetchStrategyHelper; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.CollectionDefinition; @@ -72,11 +75,9 @@ import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; import org.hibernate.persister.walking.spi.EntityIdentifierDefinition; import org.hibernate.persister.walking.spi.WalkingException; -import org.hibernate.type.CompositeType; +import org.hibernate.type.EntityType; import org.hibernate.type.Type; -import static org.hibernate.loader.spi.ResultSetProcessingContext.IdentifierResolutionContext; - /** * @author Steve Ebersole */ @@ -313,12 +314,10 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder @Override public void startingCompositeCollectionElement(CompositeCollectionElementDefinition compositeElementDefinition) { - System.out.println( - String.format( - "%s Starting composite collection element for (%s)", - StringHelper.repeat( ">>", fetchOwnerStack.size() ), - compositeElementDefinition.getCollectionDefinition().getCollectionPersister().getRole() - ) + log.tracef( + "%s Starting composite collection element for (%s)", + StringHelper.repeat( ">>", fetchOwnerStack.size() ), + compositeElementDefinition.getCollectionDefinition().getCollectionPersister().getRole() ); } @@ -397,16 +396,17 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder final Type attributeType = attributeDefinition.getType(); final boolean isComponentType = attributeType.isComponentType(); - final boolean isBasicType = ! ( isComponentType || attributeType.isAssociationType() ); + final boolean isAssociationType = attributeType.isAssociationType(); + final boolean isBasicType = ! ( isComponentType || isAssociationType ); if ( isBasicType ) { return true; } - else if ( isComponentType ) { - return handleCompositeAttribute( (CompositionDefinition) attributeDefinition ); + else if ( isAssociationType ) { + return handleAssociationAttribute( (AssociationAttributeDefinition) attributeDefinition ); } else { - return handleAssociationAttribute( (AssociationAttributeDefinition) attributeDefinition ); + return handleCompositeAttribute( (CompositionDefinition) attributeDefinition ); } } @@ -419,6 +419,24 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder ); } + @Override + public void foundAny(AssociationAttributeDefinition attributeDefinition, AnyMappingDefinition anyDefinition) { + // for ANY mappings we need to build a Fetch: + // 1) fetch type is SELECT, timing might be IMMEDIATE or DELAYED depending on whether it was defined as lazy + // 2) (because the fetch cannot be a JOIN...) do not push it to the stack + final FetchStrategy fetchStrategy = determineFetchPlan( attributeDefinition ); + + final FetchOwner fetchOwner = currentFetchOwner(); + fetchOwner.validateFetchPlan( fetchStrategy, attributeDefinition ); + + fetchOwner.buildAnyFetch( + attributeDefinition, + anyDefinition, + fetchStrategy, + this + ); + } + protected boolean handleCompositeAttribute(CompositionDefinition attributeDefinition) { final FetchOwner fetchOwner = currentFetchOwner(); final CompositeFetch fetch = fetchOwner.buildCompositeFetch( attributeDefinition, this ); @@ -427,26 +445,35 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder } protected boolean handleAssociationAttribute(AssociationAttributeDefinition attributeDefinition) { + // todo : this seems to not be correct for one-to-one final FetchStrategy fetchStrategy = determineFetchPlan( attributeDefinition ); - if ( fetchStrategy.getTiming() != FetchTiming.IMMEDIATE ) { + if ( fetchStrategy.getStyle() != FetchStyle.JOIN ) { return false; } +// if ( fetchStrategy.getTiming() != FetchTiming.IMMEDIATE ) { +// return false; +// } final FetchOwner fetchOwner = currentFetchOwner(); - fetchOwner.validateFetchPlan( fetchStrategy ); + fetchOwner.validateFetchPlan( fetchStrategy, attributeDefinition ); final Fetch associationFetch; - if ( attributeDefinition.isCollection() ) { - associationFetch = fetchOwner.buildCollectionFetch( attributeDefinition, fetchStrategy, this ); - pushToCollectionStack( (CollectionReference) associationFetch ); + final AssociationAttributeDefinition.AssociationNature nature = attributeDefinition.getAssociationNature(); + if ( nature == AssociationAttributeDefinition.AssociationNature.ANY ) { + return false; +// throw new NotYetImplementedException( "AnyType support still in progress" ); } - else { + else if ( nature == AssociationAttributeDefinition.AssociationNature.ENTITY ) { associationFetch = fetchOwner.buildEntityFetch( attributeDefinition, fetchStrategy, this ); } + else { + associationFetch = fetchOwner.buildCollectionFetch( attributeDefinition, fetchStrategy, this ); + pushToCollectionStack( (CollectionReference) associationFetch ); + } if ( FetchOwner.class.isInstance( associationFetch) ) { pushToStack( (FetchOwner) associationFetch ); @@ -521,12 +548,16 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder protected static abstract class AbstractIdentifierAttributeCollector extends AbstractFetchOwner implements FetchOwner, EntityReference, FetchStackAware { protected final EntityReference entityReference; + private final EntityPersisterBasedSqlSelectFragmentResolver sqlSelectFragmentResolver; protected final Map fetchToHydratedStateExtractorMap = new HashMap(); public AbstractIdentifierAttributeCollector(SessionFactoryImplementor sessionFactory, EntityReference entityReference) { super( sessionFactory ); this.entityReference = entityReference; + this.sqlSelectFragmentResolver = new EntityPersisterBasedSqlSelectFragmentResolver( + (Queryable) entityReference.getEntityPersister() + ); } @Override @@ -557,6 +588,15 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder throw new WalkingException( "Entity identifier cannot contain persistent collections" ); } + @Override + public AnyFetch buildAnyFetch( + AttributeDefinition attribute, + AnyMappingDefinition anyDefinition, + FetchStrategy fetchStrategy, + LoadPlanBuildingContext loadPlanBuildingContext) { + throw new WalkingException( "Entity identifier cannot contain ANY type mappings" ); + } + @Override public EntityFetch buildEntityFetch( AssociationAttributeDefinition attributeDefinition, @@ -566,25 +606,75 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder // // IMPL NOTE: we pass ourselves as the FetchOwner which will route the fetch back through our #addFetch // impl. We collect them there and later build the IdentifierDescription + + // if `this` is a fetch and its owner is "the same" (bi-directionality) as the attribute to be join fetched + // we should wrap our FetchOwner as an EntityFetch. That should solve everything except for the alias + // context lookups because of the different instances (because of wrapping). So somehow the consumer of this + // needs to be able to unwrap it to do the alias lookup, and would have to know to do that. + // + // + // we are processing the EntityReference(Address) identifier. we come across its key-many-to-one reference + // to Person. Now, if EntityReference(Address) is an instance of EntityFetch(Address) there is a strong + // likelihood that we have a bi-directionality and need to handle that specially. + // + // how to best (a) find the bi-directionality and (b) represent that? + + if ( EntityFetch.class.isInstance( entityReference ) ) { + // we just confirmed that EntityReference(Address) is an instance of EntityFetch(Address), + final EntityFetch entityFetch = (EntityFetch) entityReference; + final FetchOwner entityFetchOwner = entityFetch.getOwner(); + // so at this point we need to see if entityFetchOwner and attributeDefinition refer to the + // "same thing". "same thing" == "same type" && "same column(s)"? + // + // i make assumptions here that that the attribute type is the EntityType, is that always valid? + final EntityType attributeDefinitionTypeAsEntityType = (EntityType) attributeDefinition.getType(); + + final boolean sameType = attributeDefinitionTypeAsEntityType.getAssociatedEntityName().equals( + entityFetchOwner.retrieveFetchSourcePersister().getEntityName() + ); + + if ( sameType ) { + // check same columns as well? + + return new KeyManyToOneBidirectionalEntityFetch( + sessionFactory(), + //ugh + LockMode.READ, + this, + attributeDefinition, + (EntityReference) entityFetchOwner, + fetchStrategy + ); + } + } + final EntityFetch fetch = super.buildEntityFetch( attributeDefinition, fetchStrategy, loadPlanBuildingContext ); + + // pretty sure this HydratedCompoundValueExtractor stuff is not needed... fetchToHydratedStateExtractorMap.put( fetch, attributeDefinition.getHydratedCompoundValueExtractor() ); return fetch; } + @Override public Type getType(Fetch fetch) { - return getFetchOwnerDelegate().locateFetchMetadata( fetch ).getType(); + return fetch.getFetchedType(); } @Override public boolean isNullable(Fetch fetch) { - return getFetchOwnerDelegate().locateFetchMetadata( fetch ).isNullable(); + return fetch.isNullable(); } @Override public String[] toSqlSelectFragments(Fetch fetch, String alias) { - return getFetchOwnerDelegate().locateFetchMetadata( fetch ).toSqlSelectFragments( alias ); + return fetch.toSqlSelectFragments( alias ); + } + + @Override + public SqlSelectFragmentResolver toSqlSelectFragmentResolver() { + return sqlSelectFragmentResolver; } @Override @@ -596,8 +686,8 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder protected abstract IdentifierDescription buildIdentifierDescription(); @Override - public void validateFetchPlan(FetchStrategy fetchStrategy) { - ( (FetchOwner) entityReference ).validateFetchPlan( fetchStrategy ); + public void validateFetchPlan(FetchStrategy fetchStrategy, AttributeDefinition attributeDefinition) { + ( (FetchOwner) entityReference ).validateFetchPlan( fetchStrategy, attributeDefinition ); } @Override @@ -615,51 +705,12 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder protected static class EncapsulatedIdentifierAttributeCollector extends AbstractIdentifierAttributeCollector { private final PropertyPath propertyPath; - private final FetchOwnerDelegate delegate; public EncapsulatedIdentifierAttributeCollector( final SessionFactoryImplementor sessionFactory, final EntityReference entityReference) { super( sessionFactory, entityReference ); this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath(); - this.delegate = new AbstractFetchOwnerDelegate() { - final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType(); - - @Override - protected FetchMetadata buildFetchMetadata(Fetch fetch) { - if ( !isCompositeType ) { - throw new WalkingException( "Non-composite identifier cannot be a fetch owner" ); - } - - if ( !fetch.getOwnerPropertyName().equals( entityReference.getEntityPersister().getIdentifierPropertyName() ) ) { - throw new IllegalArgumentException( - String.format( - "Fetch owner property name [%s] is not the same as the identifier prop" + - fetch.getOwnerPropertyName(), - entityReference.getEntityPersister().getIdentifierPropertyName() - ) - ); - } - - return new FetchMetadata() { - @Override - public boolean isNullable() { - return false; - } - - @Override - public Type getType() { - return entityReference.getEntityPersister().getIdentifierType(); - } - - @Override - public String[] toSqlSelectFragments(String alias) { - // should not ever be called iiuc... - throw new WalkingException( "Should not be called" ); - } - }; - } - }; } @Override @@ -671,11 +722,6 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder ); } - @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return delegate; - } - @Override public PropertyPath getPropertyPath() { return propertyPath; @@ -684,65 +730,12 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder protected static class NonEncapsulatedIdentifierAttributeCollector extends AbstractIdentifierAttributeCollector { private final PropertyPath propertyPath; - private final FetchOwnerDelegate fetchOwnerDelegate; public NonEncapsulatedIdentifierAttributeCollector( final SessionFactoryImplementor sessionfactory, final EntityReference entityReference) { super( sessionfactory, entityReference ); this.propertyPath = ( (FetchOwner) entityReference ).getPropertyPath().append( "" ); - this.fetchOwnerDelegate = new AbstractFetchOwnerDelegate() { - final boolean isCompositeType = entityReference.getEntityPersister().getIdentifierType().isComponentType(); - final CompositeType idType = (CompositeType) entityReference.getEntityPersister().getIdentifierType(); - - - @Override - protected FetchMetadata buildFetchMetadata(Fetch fetch) { - if ( !isCompositeType ) { - throw new WalkingException( "Non-composite identifier cannot be a fetch owner" ); - } - - final int subPropertyIndex = locateSubPropertyIndex( idType, fetch.getOwnerPropertyName() ); - - return new FetchMetadata() { - final Type subType = idType.getSubtypes()[ subPropertyIndex ]; - - @Override - public boolean isNullable() { - return false; - } - - @Override - public Type getType() { - return subType; - } - - @Override - public String[] toSqlSelectFragments(String alias) { - // should not ever be called iiuc... - throw new WalkingException( "Should not be called" ); - } - }; - } - - private int locateSubPropertyIndex(CompositeType idType, String ownerPropertyName) { - for ( int i = 0; i < idType.getPropertyNames().length; i++ ) { - if ( ownerPropertyName.equals( idType.getPropertyNames()[i] ) ) { - return i; - } - } - // does not bode well if we get here... - throw new IllegalStateException( - String.format( - "Unable to locate fetched attribute [%s] as part of composite identifier [%s]", - ownerPropertyName, - getPropertyPath().getFullPath() - ) - - ); - } - - }; } @Override @@ -758,13 +751,6 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder public PropertyPath getPropertyPath() { return propertyPath; } - - - @Override - protected FetchOwnerDelegate getFetchOwnerDelegate() { - return fetchOwnerDelegate; - } - } private static class IdentifierDescriptionImpl implements IdentifierDescription { @@ -786,26 +772,31 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder return identifierFetches; } + @Override + public HydratedCompoundValueHandler getHydratedStateHandler(Fetch fetch) { + return fetchToHydratedStateExtractorMap == null ? null : fetchToHydratedStateExtractorMap.get( fetch ); + } + @Override public void hydrate(ResultSet resultSet, ResultSetProcessingContext context) throws SQLException { - final IdentifierResolutionContext ownerIdentifierResolutionContext = - context.getIdentifierResolutionContext( entityReference ); - final Object ownerIdentifierHydratedState = ownerIdentifierResolutionContext.getHydratedForm(); + final ResultSetProcessingContext.EntityReferenceProcessingState ownerEntityReferenceProcessingState = + context.getProcessingState( entityReference ); + final Object ownerIdentifierHydratedState = ownerEntityReferenceProcessingState.getIdentifierHydratedForm(); if ( ownerIdentifierHydratedState != null ) { for ( Fetch fetch : identifierFetches ) { if ( fetch instanceof EntityFetch ) { - final IdentifierResolutionContext identifierResolutionContext = - context.getIdentifierResolutionContext( (EntityFetch) fetch ); + final ResultSetProcessingContext.EntityReferenceProcessingState fetchEntityReferenceProcessingState = + context.getProcessingState( (EntityFetch) fetch ); // if the identifier was already hydrated, nothing to do - if ( identifierResolutionContext.getHydratedForm() != null ) { + if ( fetchEntityReferenceProcessingState.getIdentifierHydratedForm() != null ) { continue; } // try to extract the sub-hydrated value from the owners tuple array if ( fetchToHydratedStateExtractorMap != null && ownerIdentifierHydratedState != null ) { Serializable extracted = (Serializable) fetchToHydratedStateExtractorMap.get( fetch ) .extract( ownerIdentifierHydratedState ); - identifierResolutionContext.registerHydratedForm( extracted ); + fetchEntityReferenceProcessingState.registerIdentifierHydratedForm( extracted ); continue; } @@ -819,13 +810,40 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder return; } + final String[] columnNames; + if ( EntityFetch.class.isInstance( entityReference ) + && !FetchStrategyHelper.isJoinFetched( ((EntityFetch) entityReference).getFetchStrategy() ) ) { + final EntityFetch fetch = (EntityFetch) entityReference; + final FetchOwner fetchOwner = fetch.getOwner(); + if ( EntityReference.class.isInstance( fetchOwner ) ) { + throw new NotYetImplementedException(); +// final EntityReference ownerEntityReference = (EntityReference) fetchOwner; +// final EntityAliases ownerEntityAliases = context.getAliasResolutionContext() +// .resolveEntityColumnAliases( ownerEntityReference ); +// final int propertyIndex = ownerEntityReference.getEntityPersister() +// .getEntityMetamodel() +// .getPropertyIndex( fetch.getOwnerPropertyName() ); +// columnNames = ownerEntityAliases.getSuffixedPropertyAliases()[ propertyIndex ]; + } + else { + // todo : better message here... + throw new WalkingException( "Cannot locate association column names" ); + } + } + else { + columnNames = context.getAliasResolutionContext() + .resolveAliases( entityReference ) + .getColumnAliases() + .getSuffixedKeyAliases(); + } + final Object hydratedIdentifierState = entityReference.getEntityPersister().getIdentifierType().hydrate( resultSet, - context.getLoadQueryAliasResolutionContext().resolveEntityColumnAliases( entityReference ).getSuffixedKeyAliases(), + columnNames, context.getSession(), null ); - context.getIdentifierResolutionContext( entityReference ).registerHydratedForm( hydratedIdentifierState ); + context.getProcessingState( entityReference ).registerIdentifierHydratedForm( hydratedIdentifierState ); } @Override @@ -834,9 +852,9 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder resolveIdentifierFetch( resultSet, context, fetch ); } - final IdentifierResolutionContext ownerIdentifierResolutionContext = - context.getIdentifierResolutionContext( entityReference ); - Object hydratedState = ownerIdentifierResolutionContext.getHydratedForm(); + final ResultSetProcessingContext.EntityReferenceProcessingState ownerEntityReferenceProcessingState = + context.getProcessingState( entityReference ); + Object hydratedState = ownerEntityReferenceProcessingState.getIdentifierHydratedForm(); Serializable resolvedId = (Serializable) entityReference.getEntityPersister() .getIdentifierType() .resolve( hydratedState, context.getSession(), null ); @@ -850,14 +868,14 @@ public abstract class AbstractLoadPlanBuilderStrategy implements LoadPlanBuilder Fetch fetch) throws SQLException { if ( fetch instanceof EntityFetch ) { EntityFetch entityFetch = (EntityFetch) fetch; - final IdentifierResolutionContext identifierResolutionContext = - context.getIdentifierResolutionContext( entityFetch ); - if ( identifierResolutionContext.getEntityKey() != null ) { + final ResultSetProcessingContext.EntityReferenceProcessingState entityReferenceProcessingState = + context.getProcessingState( entityFetch ); + if ( entityReferenceProcessingState.getEntityKey() != null ) { return; } EntityKey fetchKey = entityFetch.resolveInIdentifier( resultSet, context ); - identifierResolutionContext.registerEntityKey( fetchKey ); + entityReferenceProcessingState.registerEntityKey( fetchKey ); } else if ( fetch instanceof CompositeFetch ) { for ( Fetch subFetch : ( (CompositeFetch) fetch ).getFetches() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilder.java index 19d3499d0c..4c3d025489 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilder.java @@ -29,7 +29,8 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.walking.spi.MetadataDrivenModelGraphVisitor; /** - * Coordinates building of a {@link org.hibernate.loader.plan.spi.LoadPlan} between the {@link org.hibernate.persister.walking.spi.MetadataDrivenModelGraphVisitor} and + * Coordinates building of a {@link org.hibernate.loader.plan.spi.LoadPlan} between the + * {@link org.hibernate.persister.walking.spi.MetadataDrivenModelGraphVisitor} and * {@link LoadPlanBuilderStrategy} * * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilderStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilderStrategy.java index 64bc8c486b..120af2a638 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilderStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuilderStrategy.java @@ -27,7 +27,8 @@ import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.persister.walking.spi.AssociationVisitationStrategy; /** - * Specialized {@link org.hibernate.persister.walking.spi.AssociationVisitationStrategy} implementation for building {@link org.hibernate.loader.plan.spi.LoadPlan} instances. + * Specialized {@link org.hibernate.persister.walking.spi.AssociationVisitationStrategy} implementation for + * building {@link org.hibernate.loader.plan.spi.LoadPlan} instances. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuildingContext.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuildingContext.java index 46833340d5..30f63b5f48 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuildingContext.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/LoadPlanBuildingContext.java @@ -26,8 +26,15 @@ package org.hibernate.loader.plan.spi.build; import org.hibernate.engine.spi.SessionFactoryImplementor; /** + * Provides access to context needed in building a LoadPlan. + * * @author Steve Ebersole */ public interface LoadPlanBuildingContext { + /** + * Access to the SessionFactory + * + * @return The SessionFactory + */ public SessionFactoryImplementor getSessionFactory(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/package-info.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/build/package-info.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/DelegatedLoadPlanVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/DelegatedLoadPlanVisitationStrategy.java index bfa7ff79e9..1dd773e364 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/DelegatedLoadPlanVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/DelegatedLoadPlanVisitationStrategy.java @@ -23,6 +23,7 @@ */ package org.hibernate.loader.plan.spi.visit; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CompositeFetch; @@ -115,4 +116,14 @@ public class DelegatedLoadPlanVisitationStrategy implements LoadPlanVisitationSt public void finishingCompositeFetch(CompositeFetch fetch) { returnGraphVisitationStrategy.finishingCompositeFetch( fetch ); } + + @Override + public void startingAnyFetch(AnyFetch fetch) { + returnGraphVisitationStrategy.startingAnyFetch( fetch ); + } + + @Override + public void finishingAnyFetch(AnyFetch fetch) { + returnGraphVisitationStrategy.finishingAnyFetch( fetch ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/LoadPlanVisitationStrategyAdapter.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/LoadPlanVisitationStrategyAdapter.java index ca85c40cc2..7e80079c22 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/LoadPlanVisitationStrategyAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/LoadPlanVisitationStrategyAdapter.java @@ -23,6 +23,7 @@ */ package org.hibernate.loader.plan.spi.visit; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CompositeFetch; @@ -98,4 +99,12 @@ public class LoadPlanVisitationStrategyAdapter implements LoadPlanVisitationStra @Override public void finishingCompositeFetch(CompositeFetch fetch) { } + + @Override + public void startingAnyFetch(AnyFetch fetch) { + } + + @Override + public void finishingAnyFetch(AnyFetch fetch) { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategy.java index 02d78e5597..0b7a0914b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategy.java @@ -23,6 +23,7 @@ */ package org.hibernate.loader.plan.spi.visit; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CompositeFetch; @@ -136,4 +137,18 @@ public interface ReturnGraphVisitationStrategy { * @param fetch The composite fetch */ public void finishingCompositeFetch(CompositeFetch fetch); + + /** + * Notification we are starting the processing of a ANY fetch + * + * @param fetch The ANY fetch + */ + public void startingAnyFetch(AnyFetch fetch); + + /** + * Notification that we are finishing up the processing of a ANY fetch + * + * @param fetch The ANY fetch + */ + public void finishingAnyFetch(AnyFetch fetch); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategyAdapter.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategyAdapter.java index 1432cc955d..d6f57a2ac6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategyAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitationStrategyAdapter.java @@ -23,6 +23,7 @@ */ package org.hibernate.loader.plan.spi.visit; +import org.hibernate.loader.plan.spi.AnyFetch; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.CompositeFetch; @@ -89,4 +90,12 @@ public class ReturnGraphVisitationStrategyAdapter implements ReturnGraphVisitati @Override public void finishingCompositeFetch(CompositeFetch fetch) { } + + @Override + public void startingAnyFetch(AnyFetch fetch) { + } + + @Override + public void finishingAnyFetch(AnyFetch fetch) { + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitor.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitor.java index 85036e69f9..e512167c33 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/spi/visit/ReturnGraphVisitor.java @@ -51,7 +51,7 @@ public class ReturnGraphVisitor { } } - public void visit(List rootReturns) { + public void visit(List rootReturns) { for ( Return rootReturn : rootReturns ) { visitRootReturn( rootReturn ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadPlanAdvisor.java b/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadPlanAdvisor.java index e7c0e2c60e..0d3f647d9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadPlanAdvisor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/spi/LoadPlanAdvisor.java @@ -26,7 +26,7 @@ package org.hibernate.loader.spi; import org.hibernate.loader.plan.spi.LoadPlan; /** - * An advisor that can be made available to the {@link ResultSetProcessor} and {@link ScrollableResultSetProcessor}. + * An advisor that can be made available to the {@link org.hibernate.loader.plan.exec.process.spi.ResultSetProcessor} and {@link org.hibernate.loader.plan.exec.process.spi.ScrollableResultSetProcessor}. * * The processors consult with the advisor, if one is provided, as a means to influence the load plan, meaning that * the advisor might add fetches. A caveat is that any added fetches cannot be join fetches (they cannot alter the diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/ResultSetProcessingContext.java b/hibernate-core/src/main/java/org/hibernate/loader/spi/ResultSetProcessingContext.java deleted file mode 100644 index ccb12fcc1a..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/ResultSetProcessingContext.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * Copyright (c) 2013, 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.spi; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Set; - -import org.hibernate.LockMode; -import org.hibernate.engine.spi.EntityKey; -import org.hibernate.engine.spi.QueryParameters; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.loader.EntityAliases; -import org.hibernate.loader.plan.spi.EntityReference; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.EntityType; - -/** - * @author Steve Ebersole - */ -public interface ResultSetProcessingContext { - public SessionImplementor getSession(); - - public QueryParameters getQueryParameters(); - - public EntityKey getDictatedRootEntityKey(); - - public static interface IdentifierResolutionContext { - public EntityReference getEntityReference(); - - public void registerHydratedForm(Object hydratedForm); - - public Object getHydratedForm(); - - public void registerEntityKey(EntityKey entityKey); - - public EntityKey getEntityKey(); - } - - public IdentifierResolutionContext getIdentifierResolutionContext(EntityReference entityReference); - - public Set getIdentifierResolutionContexts(); - - public LoadQueryAliasResolutionContext getLoadQueryAliasResolutionContext(); - - public void registerHydratedEntity(EntityPersister persister, EntityKey entityKey, Object entityInstance); - - public static interface EntityKeyResolutionContext { - public EntityPersister getEntityPersister(); - public LockMode getLockMode(); - public EntityReference getEntityReference(); - } - - public Object resolveEntityKey(EntityKey entityKey, EntityKeyResolutionContext entityKeyContext); - - - // should be able to get rid of the methods below here from the interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - public void checkVersion( - ResultSet resultSet, - EntityPersister persister, - EntityAliases entityAliases, - EntityKey entityKey, - Object entityInstance) throws SQLException; - - public String getConcreteEntityTypeName( - ResultSet resultSet, - EntityPersister persister, - EntityAliases entityAliases, - EntityKey entityKey) throws SQLException; - - public void loadFromResultSet( - ResultSet resultSet, - Object entityInstance, - String concreteEntityTypeName, - EntityKey entityKey, - EntityAliases entityAliases, - LockMode acquiredLockMode, - EntityPersister persister, - boolean eagerFetch, - EntityType associationType) throws SQLException; -} diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 990a2a21fc..732058a398 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -81,6 +81,8 @@ import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.walking.internal.CompositionSingularSubAttributesHelper; +import org.hibernate.persister.walking.internal.StandardAnyTypeDefinition; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.AttributeSource; import org.hibernate.persister.walking.spi.CollectionDefinition; @@ -89,6 +91,7 @@ import org.hibernate.persister.walking.spi.CollectionIndexDefinition; import org.hibernate.persister.walking.spi.CompositeCollectionElementDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; +import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.pretty.MessageHelper; import org.hibernate.sql.Alias; import org.hibernate.sql.SelectFragment; @@ -100,6 +103,7 @@ import org.hibernate.sql.ordering.antlr.FormulaReference; import org.hibernate.sql.ordering.antlr.OrderByAliasResolver; import org.hibernate.sql.ordering.antlr.OrderByTranslation; import org.hibernate.sql.ordering.antlr.SqlValueReference; +import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; @@ -2017,10 +2021,19 @@ public abstract class AbstractCollectionPersister return getElementType(); } + @Override + public AnyMappingDefinition toAnyMappingDefinition() { + final Type type = getType(); + if ( ! type.isAnyType() ) { + throw new WalkingException( "Cannot treat collection element type as ManyToAny" ); + } + return new StandardAnyTypeDefinition( (AnyType) type, isLazy() || isExtraLazy() ); + } + @Override public EntityDefinition toEntityDefinition() { if ( getType().isComponentType() ) { - throw new IllegalStateException( "Cannot treat composite collection element type as entity" ); + throw new WalkingException( "Cannot treat composite collection element type as entity" ); } return getElementPersister(); } @@ -2029,7 +2042,7 @@ public abstract class AbstractCollectionPersister public CompositeCollectionElementDefinition toCompositeElementDefinition() { if ( ! getType().isComponentType() ) { - throw new IllegalStateException( "Cannot treat entity collection element type as composite" ); + throw new WalkingException( "Cannot treat entity collection element type as composite" ); } return new CompositeCollectionElementDefinition() { @@ -2043,6 +2056,11 @@ public abstract class AbstractCollectionPersister return getElementType(); } + @Override + public boolean isNullable() { + return false; + } + @Override public AttributeSource getSource() { // TODO: what if this is a collection w/in an encapsulated composition attribute? diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 3ad089ffe0..acdfa30464 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -29,6 +29,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -2495,6 +2496,15 @@ public abstract class AbstractEntityPersister .buildLoader( this, batchSize, lockOptions, getFactory(), loadQueryInfluencers ); } + /** + * Used internally to create static loaders. These are the default set of loaders used to handle get()/load() + * processing. lock() handling is done by the LockingStrategy instances (see {@link #getLocker}) + * + * @param lockMode The lock mode to apply to the thing being loaded. + * @return + * + * @throws MappingException + */ protected UniqueEntityLoader createEntityLoader(LockMode lockMode) throws MappingException { return createEntityLoader( lockMode, LoadQueryInfluencers.NONE ); } @@ -3765,8 +3775,24 @@ public abstract class AbstractEntityPersister return StringHelper.generateAlias( getEntityName() ); } + /** + * Post-construct is a callback for AbstractEntityPersister subclasses to call after they are all done with their + * constructor processing. It allows AbstractEntityPersister to extend its construction after all subclass-specific + * details have been handled. + * + * @param mapping The mapping + * + * @throws MappingException Indicates a problem accessing the Mapping + */ protected void postConstruct(Mapping mapping) throws MappingException { - initPropertyPaths(mapping); + initPropertyPaths( mapping ); + + //doLateInit(); + prepareEntityIdentifierDefinition(); + } + + private void doLateInit() { + generateEntityDefinition(); //insert/update/delete SQL final int joinSpan = getTableSpan(); @@ -3824,11 +3850,11 @@ public abstract class AbstractEntityPersister } logStaticSQL(); - } - public void postInstantiate() throws MappingException { - generateEntityDefinition(); + public final void postInstantiate() throws MappingException { + doLateInit(); +// generateEntityDefinition(); createLoaders(); createUniqueKeyLoaders(); @@ -5112,6 +5138,9 @@ public abstract class AbstractEntityPersister private void prepareEntityIdentifierDefinition() { + if ( entityIdentifierDefinition != null ) { + return; + } final Type idType = getIdentifierType(); if ( !idType.isComponentType() ) { @@ -5131,35 +5160,119 @@ public abstract class AbstractEntityPersister EntityIdentifierDefinitionHelper.buildNonEncapsulatedCompositeIdentifierDefinition( this ); } - private void collectAttributeDefinitions() { - // todo : leverage the attribute definitions housed on EntityMetamodel - // for that to work, we'd have to be able to walk our super entity persister(s) - attributeDefinitions = new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { -// private final int numberOfAttributes = countSubclassProperties(); - private final int numberOfAttributes = entityMetamodel.getPropertySpan(); - private int currentAttributeNumber = 0; + private void collectAttributeDefinitions(List definitions, EntityMetamodel metamodel) { + for ( int i = 0; i < metamodel.getPropertySpan(); i++ ) { + definitions.add( metamodel.getProperties()[i] ); + } - @Override - public boolean hasNext() { - return currentAttributeNumber < numberOfAttributes; - } + // see if there are any subclass persisters... + final Set subClassEntityNames = metamodel.getSubclassEntityNames(); + if ( subClassEntityNames == null ) { + return; + } - @Override - public AttributeDefinition next() { - final int attributeNumber = currentAttributeNumber; - currentAttributeNumber++; - return entityMetamodel.getProperties()[ attributeNumber ]; - } - - @Override - public void remove() { - throw new UnsupportedOperationException( "Remove operation not supported here" ); - } - }; + // see if we can find the persisters... + for ( String subClassEntityName : subClassEntityNames ) { + if ( metamodel.getName().equals( subClassEntityName ) ) { + // skip it + continue; } - }; + try { + final EntityPersister subClassEntityPersister = factory.getEntityPersister( subClassEntityName ); + collectAttributeDefinitions( definitions, subClassEntityPersister.getEntityMetamodel() ); + } + catch (MappingException e) { + throw new IllegalStateException( + String.format( + "Could not locate subclass EntityPersister [%s] while processing EntityPersister [%s]", + subClassEntityName, + metamodel.getName() + ), + e + ); + } + } } + + private void collectAttributeDefinitions() { + // todo : I think this works purely based on luck atm + // specifically in terms of the sub/super class entity persister(s) being available. Bit of chicken-egg + // problem there: + // * If I do this during postConstruct (as it is now), it works as long as the + // super entity persister is already registered, but I don't think that is necessarily true. + // * If I do this during postInstantiate then lots of stuff in postConstruct breaks if we want + // to try and drive SQL generation on these (which we do ultimately). A possible solution there + // would be to delay all SQL generation until postInstantiate + + List attributeDefinitions = new ArrayList(); + collectAttributeDefinitions( attributeDefinitions, getEntityMetamodel() ); + + +// EntityMetamodel currentEntityMetamodel = this.getEntityMetamodel(); +// while ( currentEntityMetamodel != null ) { +// for ( int i = 0; i < currentEntityMetamodel.getPropertySpan(); i++ ) { +// attributeDefinitions.add( currentEntityMetamodel.getProperties()[i] ); +// } +// // see if there is a super class EntityMetamodel +// final String superEntityName = currentEntityMetamodel.getSuperclass(); +// if ( superEntityName != null ) { +// currentEntityMetamodel = factory.getEntityPersister( superEntityName ).getEntityMetamodel(); +// } +// else { +// currentEntityMetamodel = null; +// } +// } + + this.attributeDefinitions = Collections.unmodifiableList( attributeDefinitions ); +// // todo : leverage the attribute definitions housed on EntityMetamodel +// // for that to work, we'd have to be able to walk our super entity persister(s) +// this.attributeDefinitions = new Iterable() { +// @Override +// public Iterator iterator() { +// return new Iterator() { +//// private final int numberOfAttributes = countSubclassProperties(); +//// private final int numberOfAttributes = entityMetamodel.getPropertySpan(); +// +// EntityMetamodel currentEntityMetamodel = entityMetamodel; +// int numberOfAttributesInCurrentEntityMetamodel = currentEntityMetamodel.getPropertySpan(); +// +// private int currentAttributeNumber; +// +// @Override +// public boolean hasNext() { +// return currentEntityMetamodel != null +// && currentAttributeNumber < numberOfAttributesInCurrentEntityMetamodel; +// } +// +// @Override +// public AttributeDefinition next() { +// final int attributeNumber = currentAttributeNumber; +// currentAttributeNumber++; +// final AttributeDefinition next = currentEntityMetamodel.getProperties()[ attributeNumber ]; +// +// if ( currentAttributeNumber >= numberOfAttributesInCurrentEntityMetamodel ) { +// // see if there is a super class EntityMetamodel +// final String superEntityName = currentEntityMetamodel.getSuperclass(); +// if ( superEntityName != null ) { +// currentEntityMetamodel = factory.getEntityPersister( superEntityName ).getEntityMetamodel(); +// if ( currentEntityMetamodel != null ) { +// numberOfAttributesInCurrentEntityMetamodel = currentEntityMetamodel.getPropertySpan(); +// currentAttributeNumber = 0; +// } +// } +// } +// +// return next; +// } +// +// @Override +// public void remove() { +// throw new UnsupportedOperationException( "Remove operation not supported here" ); +// } +// }; +// } +// }; + } + + } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index d5f62787c6..bbe5c3e2af 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -1024,7 +1024,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { return qualifiedTableNames[ propertyTableNumbers[index] ]; } - public void postInstantiate() { + protected void doPostInstantiate() { super.postInstantiate(); if (hasSequentialSelects) { String[] entityNames = getSubclassClosure(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java index 3a68c09b7b..f3b80c14f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/CompositionSingularSubAttributesHelper.java @@ -25,6 +25,7 @@ package org.hibernate.persister.walking.internal; import java.util.Iterator; +import org.hibernate.FetchMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; @@ -38,6 +39,7 @@ import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.spi.HydratedCompoundValueHandler; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationKey; import org.hibernate.persister.walking.spi.AttributeDefinition; @@ -47,6 +49,7 @@ import org.hibernate.persister.walking.spi.CompositeCollectionElementDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; import org.hibernate.persister.walking.spi.WalkingException; +import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; import org.hibernate.type.CompositeType; import org.hibernate.type.Type; @@ -128,6 +131,8 @@ public class CompositionSingularSubAttributesHelper { final int columnSpan = type.getColumnSpan( ownerEntityPersister.getFactory() ); final String[] subAttributeLhsColumns = ArrayHelper.slice( lhsColumns, columnPosition, columnSpan ); + final boolean nullable = compositeType.getPropertyNullability()[subAttributeNumber]; + currentColumnPosition += columnSpan; if ( type.isAssociationType() ) { @@ -135,27 +140,42 @@ public class CompositionSingularSubAttributesHelper { return new AssociationAttributeDefinition() { @Override public AssociationKey getAssociationKey() { - /* TODO: is this always correct? */ - //return new AssociationKey( - // joinable.getTableName(), - // JoinHelper.getRHSColumnNames( aType, getEntityPersister().getFactory() ) - //); - return new AssociationKey( - lhsTableName, - subAttributeLhsColumns - ); + return new AssociationKey( lhsTableName, subAttributeLhsColumns ); } + @Override - public boolean isCollection() { - return false; + public AssociationNature getAssociationNature() { + if ( type.isAnyType() ) { + return AssociationNature.ANY; + } + else { + // cannot be a collection + return AssociationNature.ENTITY; + } } @Override public EntityDefinition toEntityDefinition() { + if ( getAssociationNature() != AssociationNature.ENTITY ) { + throw new WalkingException( + "Cannot build EntityDefinition from non-entity-typed attribute" + ); + } return (EntityPersister) aType.getAssociatedJoinable( ownerEntityPersister.getFactory() ); } + @Override + public AnyMappingDefinition toAnyDefinition() { + if ( getAssociationNature() != AssociationNature.ANY ) { + throw new WalkingException( + "Cannot build AnyMappingDefinition from non-any-typed attribute" + ); + } + // todo : not sure how lazy is propogated into the component for a subattribute of type any + return new StandardAnyTypeDefinition( (AnyType) aType, false ); + } + @Override public CollectionDefinition toCollectionDefinition() { throw new WalkingException( "A collection cannot be mapped to a composite ID sub-attribute." ); @@ -182,8 +202,13 @@ public class CompositionSingularSubAttributesHelper { } @Override - public Type getType() { - return type; + public AssociationType getType() { + return aType; + } + + @Override + public boolean isNullable() { + return nullable; } @Override @@ -204,6 +229,11 @@ public class CompositionSingularSubAttributesHelper { return type; } + @Override + public boolean isNullable() { + return nullable; + } + @Override public AttributeSource getSource() { return this; @@ -233,6 +263,11 @@ public class CompositionSingularSubAttributesHelper { return type; } + @Override + public boolean isNullable() { + return nullable; + } + @Override public AttributeSource getSource() { return source; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java index 6e6e7f5a2f..3a3aba11b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/EntityIdentifierDefinitionHelper.java @@ -119,6 +119,11 @@ public class EntityIdentifierDefinitionHelper { return entityPersister.getEntityMetamodel().getIdentifierProperty().getType(); } + @Override + public boolean isNullable() { + return false; + } + @Override public AttributeSource getSource() { return entityPersister; @@ -135,7 +140,6 @@ public class EntityIdentifierDefinitionHelper { } private static class CompositionDefinitionAdapter extends AttributeDefinitionAdapter implements CompositionDefinition { - CompositionDefinitionAdapter(AbstractEntityPersister entityPersister) { super( entityPersister ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java index 7d6cf84335..9ddba820c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/FetchStrategyHelper.java @@ -26,6 +26,7 @@ package org.hibernate.persister.walking.internal; import java.util.Iterator; import org.hibernate.FetchMode; +import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.profile.Fetch; @@ -149,7 +150,11 @@ public class FetchStrategyHelper { } private static boolean isSubsequentSelectDelayed(AssociationType type, SessionFactoryImplementor sessionFactory) { - if ( type.isEntityType() ) { + if ( type.isAnyType() ) { + // we'd need more context here. this is only kept as part of the property state on the owning entity + return false; + } + else if ( type.isEntityType() ) { return ( (EntityPersister) type.getAssociatedJoinable( sessionFactory ) ).hasProxy(); } else { @@ -157,4 +162,9 @@ public class FetchStrategyHelper { return cp.isLazy() || cp.isExtraLazy(); } } + + public static boolean isJoinFetched(FetchStrategy fetchStrategy) { + return fetchStrategy.getTiming() == FetchTiming.IMMEDIATE + && fetchStrategy.getStyle() == FetchStyle.JOIN; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/StandardAnyTypeDefinition.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/StandardAnyTypeDefinition.java new file mode 100644 index 0000000000..790c8f6ffe --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/internal/StandardAnyTypeDefinition.java @@ -0,0 +1,103 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.persister.walking.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.hibernate.persister.walking.spi.AnyMappingDefinition; +import org.hibernate.type.AnyType; +import org.hibernate.type.MetaType; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class StandardAnyTypeDefinition implements AnyMappingDefinition { + private final AnyType anyType; + private final boolean definedAsLazy; + private final List discriminatorMappings; + + public StandardAnyTypeDefinition(AnyType anyType, boolean definedAsLazy) { + this.anyType = anyType; + this.definedAsLazy = definedAsLazy; + this.discriminatorMappings = interpretDiscriminatorMappings( anyType ); + } + + private static List interpretDiscriminatorMappings(AnyType anyType) { + final Type discriminatorType = anyType.getDiscriminatorType(); + if ( ! MetaType.class.isInstance( discriminatorType ) ) { + return Collections.emptyList(); + } + + final MetaType metaType = (MetaType) discriminatorType; + final List discriminatorMappings = new ArrayList(); + for ( final Map.Entry entry : metaType.getDiscriminatorValuesToEntityNameMap().entrySet() ) { + discriminatorMappings.add( + new DiscriminatorMapping() { + private final Object discriminatorValue = entry.getKey(); + private final String entityName = entry.getValue(); + + @Override + public Object getDiscriminatorValue() { + return discriminatorValue; + } + + @Override + public String getEntityName() { + return entityName; + } + } + ); + } + return discriminatorMappings; + } + + @Override + public AnyType getType() { + return anyType; + } + + @Override + public boolean isLazy() { + return definedAsLazy; + } + + @Override + public Type getIdentifierType() { + return anyType.getIdentifierType(); + } + + @Override + public Type getDiscriminatorType() { + return anyType.getDiscriminatorType(); + } + + @Override + public Iterable getMappingDefinedDiscriminatorMappings() { + return discriminatorMappings; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AnyMappingDefinition.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AnyMappingDefinition.java new file mode 100644 index 0000000000..99fb3f2a9c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AnyMappingDefinition.java @@ -0,0 +1,110 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.persister.walking.spi; + +import org.hibernate.type.AnyType; +import org.hibernate.type.Type; + +/** + * Describes an ANY mapping + * + * @author Steve Ebersole + */ +public interface AnyMappingDefinition { + /** + * Access to the mapping's AnyType + * + * @return The AnyType + */ + public AnyType getType(); + + /** + * Was the mapping defined as lazy? + * + * @return true/false + */ + public boolean isLazy(); + + /** + * Access to the type of the value that makes up the identifier portion of the AnyType. + * + * @return The identifier type + * + * @see org.hibernate.annotations.AnyMetaDef#idType() + */ + public Type getIdentifierType(); + + /** + * Access to the type of the value that makes up the discriminator portion of the AnyType. The discriminator is + * historically called the "meta". + *

+ * NOTE : If explicit discriminator mappings are given, the type here will be a {@link org.hibernate.type.MetaType}. + * + * @return The discriminator type + * + * @see org.hibernate.annotations.Any#metaColumn() + * @see org.hibernate.annotations.AnyMetaDef#metaType() + */ + public Type getDiscriminatorType(); + + /** + * Access to discriminator mappings explicitly defined in the mapping metadata. + * + * There are 2 flavors of discrimination:

    + *
  1. + * The database holds the concrete entity names. This is an implicit form, meaning that the discriminator + * mappings do not have to be defined in the mapping metadata. In this case, an empty iterable is returned + * here + *
  2. + *
  3. + * The database holds discriminator values that are interpreted to corresponding entity names based on + * discriminator mappings explicitly supplied in the mapping metadata (see + * {@link org.hibernate.annotations.AnyMetaDef#metaValues()}). In this case, this method gives access + * to those explicitly defined mappings. + *
  4. + *
+ * + * @return The explicitly defined discriminator value mappings. + */ + public Iterable getMappingDefinedDiscriminatorMappings(); + + /** + * Models a single discriminator mapping definition + */ + public static interface DiscriminatorMapping { + /** + * Access to the defined discriminator value (the database value) being mapped. + * + * @return The defined discriminator value + */ + public Object getDiscriminatorValue(); + + /** + * Access to the defined entity name corresponding to the defined {@link #getDiscriminatorValue()} + * + * @return The defined entity name + */ + public String getEntityName(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationAttributeDefinition.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationAttributeDefinition.java index 719eb51803..f851640b06 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationAttributeDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationAttributeDefinition.java @@ -28,19 +28,31 @@ import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.loader.PropertyPath; import org.hibernate.persister.spi.HydratedCompoundValueHandler; +import org.hibernate.type.AssociationType; /** * @author Steve Ebersole */ public interface AssociationAttributeDefinition extends AttributeDefinition { + @Override + AssociationType getType(); + public AssociationKey getAssociationKey(); - public boolean isCollection(); + public static enum AssociationNature { + ANY, + ENTITY, + COLLECTION + } + + public AssociationNature getAssociationNature(); public EntityDefinition toEntityDefinition(); public CollectionDefinition toCollectionDefinition(); + public AnyMappingDefinition toAnyDefinition(); + public FetchStrategy determineFetchPlan(LoadQueryInfluencers loadQueryInfluencers, PropertyPath propertyPath); public CascadeStyle determineCascadeStyle(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationVisitationStrategy.java index d9d475a445..26a8a5c098 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AssociationVisitationStrategy.java @@ -60,4 +60,6 @@ public interface AssociationVisitationStrategy { public boolean startingAttribute(AttributeDefinition attributeDefinition); public void finishingAttribute(AttributeDefinition attributeDefinition); + + public void foundAny(AssociationAttributeDefinition attributeDefinition, AnyMappingDefinition anyDefinition); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AttributeDefinition.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AttributeDefinition.java index ed58612671..ecea94881c 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AttributeDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/AttributeDefinition.java @@ -29,7 +29,8 @@ import org.hibernate.type.Type; * @author Steve Ebersole */ public interface AttributeDefinition { + public AttributeSource getSource(); public String getName(); public Type getType(); - public AttributeSource getSource(); + public boolean isNullable(); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/CollectionElementDefinition.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/CollectionElementDefinition.java index 94e05cc242..bb685d1816 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/CollectionElementDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/CollectionElementDefinition.java @@ -44,6 +44,20 @@ public interface CollectionElementDefinition { */ public Type getType(); + /** + * If the element type returned by {@link #getType()} is an + * {@link org.hibernate.type.EntityType}, then the entity + * definition for the collection element is returned; + * otherwise, IllegalStateException is thrown. + * + * @return the entity definition for the collection element. + * + * @throws IllegalStateException if the collection element type + * returned by {@link #getType()} is not of type + * {@link org.hibernate.type.EntityType}. + */ + public AnyMappingDefinition toAnyMappingDefinition(); + /** * If the element type returned by {@link #getType()} is an * {@link org.hibernate.type.EntityType}, then the entity diff --git a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java index ee6e494d8a..89453781b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/walking/spi/MetadataDrivenModelGraphVisitor.java @@ -28,6 +28,7 @@ import java.util.Set; import org.jboss.logging.Logger; +import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.loader.PropertyPath; import org.hibernate.persister.collection.CollectionPersister; @@ -104,6 +105,10 @@ public class MetadataDrivenModelGraphVisitor { } private void visitAttributes(AttributeSource attributeSource) { + final Iterable attributeDefinitions = attributeSource.getAttributes(); + if ( attributeDefinitions == null ) { + return; + } for ( AttributeDefinition attributeDefinition : attributeSource.getAttributes() ) { visitAttributeDefinition( attributeDefinition ); } @@ -126,10 +131,11 @@ public class MetadataDrivenModelGraphVisitor { final PropertyPath old = currentPropertyPath; currentPropertyPath = subPath; try { - if ( attributeDefinition.getType().isAssociationType() ) { + final Type attributeType = attributeDefinition.getType(); + if ( attributeType.isAssociationType() ) { visitAssociation( (AssociationAttributeDefinition) attributeDefinition ); } - else if ( attributeDefinition.getType().isComponentType() ) { + else if ( attributeType.isComponentType() ) { visitCompositeDefinition( (CompositionDefinition) attributeDefinition ); } } @@ -145,7 +151,11 @@ public class MetadataDrivenModelGraphVisitor { addAssociationKey( attribute.getAssociationKey() ); - if ( attribute.isCollection() ) { + final AssociationAttributeDefinition.AssociationNature nature = attribute.getAssociationNature(); + if ( nature == AssociationAttributeDefinition.AssociationNature.ANY ) { + visitAnyDefinition( attribute, attribute.toAnyDefinition() ); + } + else if ( nature == AssociationAttributeDefinition.AssociationNature.COLLECTION ) { visitCollectionDefinition( attribute.toCollectionDefinition() ); } else { @@ -153,6 +163,10 @@ public class MetadataDrivenModelGraphVisitor { } } + private void visitAnyDefinition(AssociationAttributeDefinition attributeDefinition, AnyMappingDefinition anyDefinition) { + strategy.foundAny( attributeDefinition, anyDefinition ); + } + private void visitCompositeDefinition(CompositionDefinition compositionDefinition) { strategy.startingComposite( compositionDefinition ); diff --git a/hibernate-core/src/main/java/org/hibernate/property/BasicPropertyAccessor.java b/hibernate-core/src/main/java/org/hibernate/property/BasicPropertyAccessor.java index 15523c2437..89237cf61f 100644 --- a/hibernate-core/src/main/java/org/hibernate/property/BasicPropertyAccessor.java +++ b/hibernate-core/src/main/java/org/hibernate/property/BasicPropertyAccessor.java @@ -33,6 +33,7 @@ import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.PropertyAccessException; import org.hibernate.PropertyNotFoundException; +import org.hibernate.PropertySetterAccessException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.CoreMessageLogger; @@ -114,14 +115,16 @@ public class BasicPropertyAccessor implements PropertyAccessor { ); } else { - LOG.illegalPropertySetterArgument(clazz.getName(), propertyName); - LOG.expectedType(method.getParameterTypes()[0].getName(), value == null ? null : value.getClass().getName()); - throw new PropertyAccessException( + final Class expectedType = method.getParameterTypes()[0]; + LOG.illegalPropertySetterArgument( clazz.getName(), propertyName ); + LOG.expectedType( expectedType.getName(), value == null ? null : value.getClass().getName() ); + throw new PropertySetterAccessException( iae, - "IllegalArgumentException occurred while calling", - true, clazz, - propertyName + propertyName, + expectedType, + target, + value ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java index baf2bb7315..3544ed67e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ANSIJoinFragment.java @@ -50,14 +50,22 @@ public class ANSIJoinFragment extends JoinFragment { /** * Adds a join, represented by the given information, to the fragment. * - * @param tableName The name of the table being joined. - * @param alias The alias applied to the table being joined. - * @param fkColumns The columns (from the table being joined) used to define the join-restriction (the ON) - * @param pkColumns The columns (from the table being joined to) used to define the join-restriction (the ON) + * @param rhsTableName The name of the table being joined (the RHS table). + * @param rhsAlias The alias applied to the table being joined (the alias for the RHS table). + * @param lhsColumns The columns (from the table being joined) used to define the join-restriction (the ON). These + * are the LHS columns, and are expected to be qualified. + * @param rhsColumns The columns (from the table being joined to) used to define the join-restriction (the ON). These + * are the RHS columns and are expected to *not* be qualified. * @param joinType The type of join to produce (INNER, etc). * @param on Any extra join restrictions */ - public void addJoin(String tableName, String alias, String[] fkColumns, String[] pkColumns, JoinType joinType, String on) { + public void addJoin( + String rhsTableName, + String rhsAlias, + String[] lhsColumns, + String[] rhsColumns, + JoinType joinType, + String on) { final String joinString; switch (joinType) { case INNER_JOIN: @@ -77,19 +85,19 @@ public class ANSIJoinFragment extends JoinFragment { } this.buffer.append(joinString) - .append(tableName) + .append(rhsTableName) .append(' ') - .append(alias) + .append(rhsAlias) .append(" on "); - for ( int j=0; j0 ) buffer.append(" or "); - buffer.append("(") - .append( fragment.toFragmentString() ) - .append(")"); + addCondition( fragment.toFragmentString() ); + return this; + } + + public DisjunctionFragment addCondition(String fragment) { + if ( buffer.length() > 0 ) { + buffer.append(" or "); + } + buffer.append( '(' ) + .append( fragment ) + .append( ')' ); return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java index d2bf2c4526..bceaf833af 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/AbstractCompositionAttribute.java @@ -25,7 +25,9 @@ package org.hibernate.tuple.component; import java.util.Iterator; +import org.hibernate.engine.internal.JoinHelper; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; @@ -98,22 +100,54 @@ public abstract class AbstractCompositionAttribute extends AbstractNonIdentifier final AssociationKey associationKey; final AssociationType aType = (AssociationType) type; final Joinable joinable = aType.getAssociatedJoinable( sessionFactory() ); - if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT ) { + + if ( aType.isAnyType() ) { associationKey = new AssociationKey( - getLHSTableName( + JoinHelper.getLHSTableName( aType, attributeNumber(), - (OuterJoinLoadable) locateOwningPersister() + (OuterJoinLoadable) getSource() ), - getLHSColumnNames( + JoinHelper.getLHSColumnNames( aType, attributeNumber(), - columnPosition, - (OuterJoinLoadable) locateOwningPersister(), + 0, + (OuterJoinLoadable) getSource(), sessionFactory() ) ); } + else if ( aType.getForeignKeyDirection() == ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT ) { + final String lhsTableName; + final String[] lhsColumnNames; + + if ( joinable.isCollection() ) { + final QueryableCollection collectionPersister = (QueryableCollection) joinable; + lhsTableName = collectionPersister.getTableName(); + lhsColumnNames = collectionPersister.getElementColumnNames(); + } + else { + final OuterJoinLoadable entityPersister = (OuterJoinLoadable) locateOwningPersister(); + lhsTableName = getLHSTableName( aType, attributeNumber(), entityPersister ); + lhsColumnNames = getLHSColumnNames( aType, attributeNumber(), entityPersister, sessionFactory() ); + } + associationKey = new AssociationKey( lhsTableName, lhsColumnNames ); + +// associationKey = new AssociationKey( +// getLHSTableName( +// aType, +// attributeNumber(), +// (OuterJoinLoadable) locateOwningPersister() +// ), +// getLHSColumnNames( +// aType, +// attributeNumber(), +// columnPosition, +// (OuterJoinLoadable) locateOwningPersister(), +// sessionFactory() +// ) +// ); + } else { associationKey = new AssociationKey( joinable.getTableName(), @@ -121,6 +155,11 @@ public abstract class AbstractCompositionAttribute extends AbstractNonIdentifier ); } + final CompositeType cType = getType(); + final boolean nullable = cType.getPropertyNullability() == null + ? true + : cType.getPropertyNullability()[ subAttributeNumber ]; + return new CompositeBasedAssociationAttribute( AbstractCompositionAttribute.this, sessionFactory(), @@ -132,7 +171,7 @@ public abstract class AbstractCompositionAttribute extends AbstractNonIdentifier .setUpdateable( AbstractCompositionAttribute.this.isUpdateable() ) .setInsertGenerated( AbstractCompositionAttribute.this.isInsertGenerated() ) .setUpdateGenerated( AbstractCompositionAttribute.this.isUpdateGenerated() ) - .setNullable( getType().getPropertyNullability()[subAttributeNumber] ) + .setNullable( nullable ) .setDirtyCheckable( true ) .setVersionable( AbstractCompositionAttribute.this.isVersionable() ) .setCascadeStyle( getType().getCascadeStyle( subAttributeNumber ) ) @@ -163,6 +202,11 @@ public abstract class AbstractCompositionAttribute extends AbstractNonIdentifier ); } else { + final CompositeType cType = getType(); + final boolean nullable = cType.getPropertyNullability() == null + ? true + : cType.getPropertyNullability()[ subAttributeNumber ]; + return new CompositeBasedBasicAttribute( AbstractCompositionAttribute.this, sessionFactory(), @@ -174,7 +218,7 @@ public abstract class AbstractCompositionAttribute extends AbstractNonIdentifier .setUpdateable( AbstractCompositionAttribute.this.isUpdateable() ) .setInsertGenerated( AbstractCompositionAttribute.this.isInsertGenerated() ) .setUpdateGenerated( AbstractCompositionAttribute.this.isUpdateGenerated() ) - .setNullable( getType().getPropertyNullability()[subAttributeNumber] ) + .setNullable( nullable ) .setDirtyCheckable( true ) .setVersionable( AbstractCompositionAttribute.this.isVersionable() ) .setCascadeStyle( getType().getCascadeStyle( subAttributeNumber ) ) diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java index 72eb5eac0b..d3d52ecc3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/component/CompositeBasedAssociationAttribute.java @@ -36,11 +36,15 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.spi.HydratedCompoundValueHandler; import org.hibernate.persister.walking.internal.FetchStrategyHelper; +import org.hibernate.persister.walking.internal.StandardAnyTypeDefinition; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationKey; import org.hibernate.persister.walking.spi.CollectionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; +import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.tuple.BaselineAttributeInformation; +import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; import org.hibernate.type.CompositeType; @@ -85,8 +89,39 @@ public class CompositeBasedAssociationAttribute } @Override - public boolean isCollection() { - return getJoinable().isCollection(); + public AssociationNature getAssociationNature() { + if ( getType().isAnyType() ) { + return AssociationNature.ANY; + } + else { + if ( getJoinable().isCollection() ) { + return AssociationNature.COLLECTION; + } + else { + return AssociationNature.ENTITY; + } + } + } + + private boolean isAnyType() { + return getAssociationNature() == AssociationNature.ANY; + } + + private boolean isEntityType() { + return getAssociationNature() == AssociationNature.ENTITY; + } + + private boolean isCollection() { + return getAssociationNature() == AssociationNature.COLLECTION; + } + + @Override + public AnyMappingDefinition toAnyDefinition() { + if ( !isAnyType() ) { + throw new WalkingException( "Cannot build AnyMappingDefinition from non-any-typed attribute" ); + } + // todo : not sure how lazy is propogated into the component for a subattribute of type any + return new StandardAnyTypeDefinition( (AnyType) getType(), false ); } @Override @@ -94,14 +129,20 @@ public class CompositeBasedAssociationAttribute if ( isCollection() ) { throw new IllegalStateException( "Cannot treat collection attribute as entity type" ); } + if ( isAnyType() ) { + throw new IllegalStateException( "Cannot treat any-type attribute as entity type" ); + } return (EntityPersister) getJoinable(); } @Override public CollectionDefinition toCollectionDefinition() { - if ( isCollection() ) { + if ( isEntityType() ) { throw new IllegalStateException( "Cannot treat entity attribute as collection type" ); } + if ( isAnyType() ) { + throw new IllegalStateException( "Cannot treat any-type attribute as collection type" ); + } return (CollectionPersister) getJoinable(); } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java index 78d803e43b..3e1092e387 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityBasedAssociationAttribute.java @@ -25,6 +25,7 @@ package org.hibernate.tuple.entity; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; +import org.hibernate.engine.internal.JoinHelper; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -35,11 +36,15 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.spi.HydratedCompoundValueHandler; import org.hibernate.persister.walking.internal.FetchStrategyHelper; +import org.hibernate.persister.walking.internal.StandardAnyTypeDefinition; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationKey; import org.hibernate.persister.walking.spi.CollectionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; +import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.tuple.BaselineAttributeInformation; +import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; import org.hibernate.type.ForeignKeyDirection; @@ -54,7 +59,6 @@ public class EntityBasedAssociationAttribute extends AbstractEntityBasedAttribute implements AssociationAttributeDefinition { - private Joinable joinable; public EntityBasedAssociationAttribute( EntityPersister source, @@ -70,17 +74,23 @@ public class EntityBasedAssociationAttribute public AssociationType getType() { return (AssociationType) super.getType(); } - - protected Joinable getJoinable() { - if ( joinable == null ) { - joinable = getType().getAssociatedJoinable( sessionFactory() ); - } - return joinable; - } - @Override public AssociationKey getAssociationKey() { final AssociationType type = getType(); + + if ( type.isAnyType() ) { + return new AssociationKey( + JoinHelper.getLHSTableName( type, attributeNumber(), (OuterJoinLoadable) getSource() ), + JoinHelper.getLHSColumnNames( + type, + attributeNumber(), + 0, + (OuterJoinLoadable) getSource(), + sessionFactory() + ) + ); + } + final Joinable joinable = type.getAssociatedJoinable( sessionFactory() ); if ( type.getForeignKeyDirection() == ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT ) { @@ -105,13 +115,47 @@ public class EntityBasedAssociationAttribute } @Override - public boolean isCollection() { - return getJoinable().isCollection(); + public AssociationNature getAssociationNature() { + if ( getType().isAnyType() ) { + return AssociationNature.ANY; + } + else { + if ( getType().isCollectionType() ) { + return AssociationNature.COLLECTION; + } + else { + return AssociationNature.ENTITY; + } + } + } + + @Override + public AnyMappingDefinition toAnyDefinition() { + return new StandardAnyTypeDefinition( + (AnyType) getType(), + getSource().getEntityMetamodel().getProperties()[ attributeNumber() ].isLazy() + ); + } + + private Joinable joinable; + + protected Joinable getJoinable() { + if ( getAssociationNature() == AssociationNature.ANY ) { + throw new WalkingException( "Cannot resolve AnyType to a Joinable" ); + } + + if ( joinable == null ) { + joinable = getType().getAssociatedJoinable( sessionFactory() ); + } + return joinable; } @Override public EntityDefinition toEntityDefinition() { - if ( isCollection() ) { + if ( getAssociationNature() == AssociationNature.ANY ) { + throw new WalkingException( "Cannot treat any-type attribute as an entity type" ); + } + if ( getAssociationNature() == AssociationNature.COLLECTION ) { throw new IllegalStateException( "Cannot treat collection-valued attribute as entity type" ); } return (EntityPersister) getJoinable(); @@ -119,7 +163,10 @@ public class EntityBasedAssociationAttribute @Override public CollectionDefinition toCollectionDefinition() { - if ( ! isCollection() ) { + if ( getAssociationNature() == AssociationNature.ANY ) { + throw new WalkingException( "Cannot treat any-type attribute as a collection type" ); + } + if ( getAssociationNature() == AssociationNature.ENTITY ) { throw new IllegalStateException( "Cannot treat entity-valued attribute as collection type" ); } return (QueryableCollection) getJoinable(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index 33a9bec9e0..b8a25c54ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -34,6 +34,7 @@ import java.util.Map; import org.dom4j.Node; import org.hibernate.EntityMode; +import org.hibernate.EntityNameResolver; import org.hibernate.FetchMode; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -46,8 +47,11 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.metamodel.relational.Size; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; +import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxyHelper; +import org.hibernate.proxy.LazyInitializer; /** * Handles "any" mappings @@ -55,352 +59,469 @@ import org.hibernate.proxy.HibernateProxyHelper; * @author Gavin King */ public class AnyType extends AbstractType implements CompositeType, AssociationType { + private final TypeFactory.TypeScope scope; private final Type identifierType; - private final Type metaType; + private final Type discriminatorType; - public AnyType(Type metaType, Type identifierType) { + /** + * Intended for use only from legacy {@link ObjectType} type definition + * + * @param discriminatorType + * @param identifierType + */ + protected AnyType(Type discriminatorType, Type identifierType) { + this( null, discriminatorType, identifierType ); + } + + public AnyType(TypeFactory.TypeScope scope, Type discriminatorType, Type identifierType) { + this.scope = scope; + this.discriminatorType = discriminatorType; this.identifierType = identifierType; - this.metaType = metaType; } - public Object deepCopy(Object value, SessionFactoryImplementor factory) - throws HibernateException { - return value; - } - - public boolean isMethodOf(Method method) { - return false; + public Type getIdentifierType() { + return identifierType; } - public boolean isSame(Object x, Object y) throws HibernateException { - return x==y; + public Type getDiscriminatorType() { + return discriminatorType; } - public int compare(Object x, Object y) { - return 0; //TODO: entities CAN be compared, by PK and entity name, fix this! - } - public int getColumnSpan(Mapping session) - throws MappingException { - return 2; - } + // general Type metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override public String getName() { return "object"; } - public boolean isMutable() { - return false; - } - - public Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - - throw new UnsupportedOperationException("object is a multicolumn type"); - } - - public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - return resolveAny( - (String) metaType.nullSafeGet(rs, names[0], session, owner), - (Serializable) identifierType.nullSafeGet(rs, names[1], session, owner), - session - ); - } - - public Object hydrate(ResultSet rs, String[] names, SessionImplementor session, Object owner) - throws HibernateException, SQLException { - String entityName = (String) metaType.nullSafeGet(rs, names[0], session, owner); - Serializable id = (Serializable) identifierType.nullSafeGet(rs, names[1], session, owner); - return new ObjectTypeCacheEntry(entityName, id); - } - - public Object resolve(Object value, SessionImplementor session, Object owner) - throws HibernateException { - ObjectTypeCacheEntry holder = (ObjectTypeCacheEntry) value; - return resolveAny(holder.entityName, holder.id, session); - } - - public Object semiResolve(Object value, SessionImplementor session, Object owner) - throws HibernateException { - throw new UnsupportedOperationException("any mappings may not form part of a property-ref"); - } - - private Object resolveAny(String entityName, Serializable id, SessionImplementor session) - throws HibernateException { - return entityName==null || id==null ? - null : session.internalLoad( entityName, id, false, false ); - } - - public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) - throws HibernateException, SQLException { - nullSafeSet(st, value, index, null, session); - } - - public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SessionImplementor session) - throws HibernateException, SQLException { - - Serializable id; - String entityName; - if (value==null) { - id=null; - entityName=null; - } - else { - entityName = session.bestGuessEntityName(value); - id = ForeignKeys.getEntityIdentifierIfNotUnsaved(entityName, value, session); - } - - // metaType is assumed to be single-column type - if ( settable==null || settable[0] ) { - metaType.nullSafeSet(st, entityName, index, session); - } - if (settable==null) { - identifierType.nullSafeSet(st, id, index+1, session); - } - else { - boolean[] idsettable = new boolean[ settable.length-1 ]; - System.arraycopy(settable, 1, idsettable, 0, idsettable.length); - identifierType.nullSafeSet(st, id, index+1, idsettable, session); - } - } - + @Override public Class getReturnedClass() { return Object.class; } + @Override public int[] sqlTypes(Mapping mapping) throws MappingException { - return ArrayHelper.join( - metaType.sqlTypes( mapping ), - identifierType.sqlTypes( mapping ) - ); + return ArrayHelper.join( discriminatorType.sqlTypes( mapping ), identifierType.sqlTypes( mapping ) ); } @Override public Size[] dictatedSizes(Mapping mapping) throws MappingException { - return ArrayHelper.join( - metaType.dictatedSizes( mapping ), - identifierType.dictatedSizes( mapping ) - ); + return ArrayHelper.join( discriminatorType.dictatedSizes( mapping ), identifierType.dictatedSizes( mapping ) ); } @Override public Size[] defaultSizes(Mapping mapping) throws MappingException { - return ArrayHelper.join( - metaType.defaultSizes( mapping ), - identifierType.defaultSizes( mapping ) - ); + return ArrayHelper.join( discriminatorType.defaultSizes( mapping ), identifierType.defaultSizes( mapping ) ); } - public void setToXMLNode(Node xml, Object value, SessionFactoryImplementor factory) { - throw new UnsupportedOperationException("any types cannot be stringified"); - } - - public String toLoggableString(Object value, SessionFactoryImplementor factory) - throws HibernateException { - //TODO: terrible implementation! - return value == null - ? "null" - : factory.getTypeHelper() - .entity( HibernateProxyHelper.getClassWithoutInitializingProxy( value ) ) - .toLoggableString( value, factory ); - } - - public Object fromXMLNode(Node xml, Mapping factory) throws HibernateException { - throw new UnsupportedOperationException(); //TODO: is this right?? - } - - public static final class ObjectTypeCacheEntry implements Serializable { - String entityName; - Serializable id; - ObjectTypeCacheEntry(String entityName, Serializable id) { - this.entityName = entityName; - this.id = id; - } - } - - public Object assemble( - Serializable cached, - SessionImplementor session, - Object owner) - throws HibernateException { - - ObjectTypeCacheEntry e = (ObjectTypeCacheEntry) cached; - return e==null ? null : session.internalLoad(e.entityName, e.id, false, false); - } - - public Serializable disassemble(Object value, SessionImplementor session, Object owner) - throws HibernateException { - return value==null ? - null : - new ObjectTypeCacheEntry( - session.bestGuessEntityName(value), - ForeignKeys.getEntityIdentifierIfNotUnsaved( - session.bestGuessEntityName(value), value, session - ) - ); + @Override + public Object[] getPropertyValues(Object component, EntityMode entityMode) { + throw new UnsupportedOperationException(); } + @Override public boolean isAnyType() { return true; } - public Object replace( - Object original, - Object target, - SessionImplementor session, - Object owner, - Map copyCache) - throws HibernateException { - if (original==null) { + @Override + public boolean isAssociationType() { + return true; + } + + @Override + public boolean isComponentType() { + return true; + } + + @Override + public boolean isEmbedded() { + return false; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Object deepCopy(Object value, SessionFactoryImplementor factory) { + return value; + } + + + // general Type functionality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public int compare(Object x, Object y) { + if ( x == null ) { + // if y is also null, return that they are the same (no option for "UNKNOWN") + // if y is not null, return that y is "greater" (-1 because the result is from the perspective of + // the first arg: x) + return y == null ? 0 : -1; + } + else if ( y == null ) { + // x is not null, but y is. return that x is "greater" + return 1; + } + + // At this point we know both are non-null. + final Object xId = extractIdentifier( x ); + final Object yId = extractIdentifier( y ); + + return getIdentifierType().compare( xId, yId ); + } + + private Object extractIdentifier(Object entity) { + final EntityPersister concretePersister = guessEntityPersister( entity ); + return concretePersister == null + ? null + : concretePersister.getEntityTuplizer().getIdentifier( entity, null ); + } + + private EntityPersister guessEntityPersister(Object object) { + if ( scope == null ) { + return null; + } + + String entityName = null; + + // this code is largely copied from Session's bestGuessEntityName + Object entity = object; + if ( entity instanceof HibernateProxy ) { + final LazyInitializer initializer = ( (HibernateProxy) entity ).getHibernateLazyInitializer(); + if ( initializer.isUninitialized() ) { + entityName = initializer.getEntityName(); + } + entity = initializer.getImplementation(); + } + + if ( entityName == null ) { + for ( EntityNameResolver resolver : scope.resolveFactory().iterateEntityNameResolvers() ) { + entityName = resolver.resolveEntityName( entity ); + if ( entityName != null ) { + break; + } + } + } + + if ( entityName == null ) { + // the old-time stand-by... + entityName = object.getClass().getName(); + } + + return scope.resolveFactory().getEntityPersister( entityName ); + } + + @Override + public boolean isSame(Object x, Object y) throws HibernateException { + return x == y; + } + + @Override + public boolean isModified(Object old, Object current, boolean[] checkable, SessionImplementor session) + throws HibernateException { + if ( current == null ) { + return old != null; + } + else if ( old == null ) { + return true; + } + + final ObjectTypeCacheEntry holder = (ObjectTypeCacheEntry) old; + final boolean[] idCheckable = new boolean[checkable.length-1]; + System.arraycopy( checkable, 1, idCheckable, 0, idCheckable.length ); + return ( checkable[0] && !holder.entityName.equals( session.bestGuessEntityName( current ) ) ) + || identifierType.isModified( holder.id, getIdentifier( current, session ), idCheckable, session ); + } + + @Override + public boolean[] toColumnNullness(Object value, Mapping mapping) { + final boolean[] result = new boolean[ getColumnSpan( mapping ) ]; + if ( value != null ) { + Arrays.fill( result, true ); + } + return result; + } + + @Override + public boolean isDirty(Object old, Object current, boolean[] checkable, SessionImplementor session) + throws HibernateException { + return isDirty( old, current, session ); + } + + @Override + public int getColumnSpan(Mapping session) { + return 2; + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + return resolveAny( + (String) discriminatorType.nullSafeGet( rs, names[0], session, owner ), + (Serializable) identifierType.nullSafeGet( rs, names[1], session, owner ), + session + ); + } + + @Override + public Object hydrate(ResultSet rs, String[] names, SessionImplementor session, Object owner) + throws HibernateException, SQLException { + final String entityName = (String) discriminatorType.nullSafeGet( rs, names[0], session, owner ); + final Serializable id = (Serializable) identifierType.nullSafeGet( rs, names[1], session, owner ); + return new ObjectTypeCacheEntry( entityName, id ); + } + + @Override + public Object resolve(Object value, SessionImplementor session, Object owner) throws HibernateException { + final ObjectTypeCacheEntry holder = (ObjectTypeCacheEntry) value; + return resolveAny( holder.entityName, holder.id, session ); + } + + private Object resolveAny(String entityName, Serializable id, SessionImplementor session) + throws HibernateException { + return entityName==null || id==null + ? null + : session.internalLoad( entityName, id, false, false ); + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) + throws HibernateException, SQLException { + nullSafeSet( st, value, index, null, session ); + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SessionImplementor session) + throws HibernateException, SQLException { + Serializable id; + String entityName; + if ( value == null ) { + id = null; + entityName = null; + } + else { + entityName = session.bestGuessEntityName( value ); + id = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, value, session ); + } + + // discriminatorType is assumed to be single-column type + if ( settable == null || settable[0] ) { + discriminatorType.nullSafeSet( st, entityName, index, session ); + } + if ( settable == null ) { + identifierType.nullSafeSet( st, id, index+1, session ); + } + else { + final boolean[] idSettable = new boolean[ settable.length-1 ]; + System.arraycopy( settable, 1, idSettable, 0, idSettable.length ); + identifierType.nullSafeSet( st, id, index+1, idSettable, session ); + } + } + + @Override + public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException { + //TODO: terrible implementation! + return value == null + ? "null" + : factory.getTypeHelper() + .entity( HibernateProxyHelper.getClassWithoutInitializingProxy( value ) ) + .toLoggableString( value, factory ); + } + + @Override + public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException { + final ObjectTypeCacheEntry e = (ObjectTypeCacheEntry) cached; + return e == null ? null : session.internalLoad( e.entityName, e.id, false, false ); + } + + @Override + public Serializable disassemble(Object value, SessionImplementor session, Object owner) throws HibernateException { + if ( value == null ) { return null; } else { - String entityName = session.bestGuessEntityName(original); - Serializable id = ForeignKeys.getEntityIdentifierIfNotUnsaved( - entityName, - original, - session + return new ObjectTypeCacheEntry( + session.bestGuessEntityName( value ), + ForeignKeys.getEntityIdentifierIfNotUnsaved( + session.bestGuessEntityName( value ), + value, + session + ) ); - return session.internalLoad( - entityName, - id, - false, - false - ); } } - public CascadeStyle getCascadeStyle(int i) { - return CascadeStyles.NONE; + + @Override + public Object replace(Object original, Object target, SessionImplementor session, Object owner, Map copyCache) + throws HibernateException { + if ( original == null ) { + return null; + } + else { + final String entityName = session.bestGuessEntityName( original ); + final Serializable id = ForeignKeys.getEntityIdentifierIfNotUnsaved( entityName, original, session ); + return session.internalLoad( entityName, id, false, false ); + } } - public FetchMode getFetchMode(int i) { - return FetchMode.SELECT; + @Override + public Object nullSafeGet(ResultSet rs, String name, SessionImplementor session, Object owner) { + throw new UnsupportedOperationException( "object is a multicolumn type" ); + } + + @Override + public Object semiResolve(Object value, SessionImplementor session, Object owner) { + throw new UnsupportedOperationException( "any mappings may not form part of a property-ref" ); + } + + @Override + public void setToXMLNode(Node xml, Object value, SessionFactoryImplementor factory) { + throw new UnsupportedOperationException("any types cannot be stringified"); + } + + @Override + public Object fromXMLNode(Node xml, Mapping factory) throws HibernateException { + throw new UnsupportedOperationException(); + } + + + + // CompositeType implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public boolean isMethodOf(Method method) { + return false; } private static final String[] PROPERTY_NAMES = new String[] { "class", "id" }; + @Override public String[] getPropertyNames() { return PROPERTY_NAMES; } - public Object getPropertyValue(Object component, int i, SessionImplementor session) - throws HibernateException { - - return i==0 ? - session.bestGuessEntityName(component) : - getIdentifier(component, session); + @Override + public Object getPropertyValue(Object component, int i, SessionImplementor session) throws HibernateException { + return i==0 + ? session.bestGuessEntityName( component ) + : getIdentifier( component, session ); } - public Object[] getPropertyValues(Object component, SessionImplementor session) - throws HibernateException { - - return new Object[] { session.bestGuessEntityName(component), getIdentifier(component, session) }; + @Override + public Object[] getPropertyValues(Object component, SessionImplementor session) throws HibernateException { + return new Object[] { + session.bestGuessEntityName( component ), + getIdentifier( component, session ) + }; } private Serializable getIdentifier(Object value, SessionImplementor session) throws HibernateException { try { - return ForeignKeys.getEntityIdentifierIfNotUnsaved( session.bestGuessEntityName(value), value, session ); + return ForeignKeys.getEntityIdentifierIfNotUnsaved( + session.bestGuessEntityName( value ), + value, + session + ); } catch (TransientObjectException toe) { return null; } } + @Override + public void setPropertyValues(Object component, Object[] values, EntityMode entityMode) { + throw new UnsupportedOperationException(); + } + + private static final boolean[] NULLABILITY = new boolean[] { false, false }; + + @Override + public boolean[] getPropertyNullability() { + return NULLABILITY; + } + + @Override public Type[] getSubtypes() { - return new Type[] { metaType, identifierType }; + return new Type[] {discriminatorType, identifierType }; } - public void setPropertyValues(Object component, Object[] values, EntityMode entityMode) - throws HibernateException { - - throw new UnsupportedOperationException(); - + @Override + public CascadeStyle getCascadeStyle(int i) { + return CascadeStyles.NONE; } - public Object[] getPropertyValues(Object component, EntityMode entityMode) { - throw new UnsupportedOperationException(); + @Override + public FetchMode getFetchMode(int i) { + return FetchMode.SELECT; } - public boolean isComponentType() { - return true; - } + // AssociationType implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override public ForeignKeyDirection getForeignKeyDirection() { - //return AssociationType.FOREIGN_KEY_TO_PARENT; //this is better but causes a transient object exception... return ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT; } - public boolean isAssociationType() { - return true; - } - + @Override public boolean useLHSPrimaryKey() { return false; } - public Joinable getAssociatedJoinable(SessionFactoryImplementor factory) { - throw new UnsupportedOperationException("any types do not have a unique referenced persister"); - } - - public boolean isModified(Object old, Object current, boolean[] checkable, SessionImplementor session) - throws HibernateException { - if (current==null) return old!=null; - if (old==null) return current!=null; - ObjectTypeCacheEntry holder = (ObjectTypeCacheEntry) old; - boolean[] idcheckable = new boolean[checkable.length-1]; - System.arraycopy(checkable, 1, idcheckable, 0, idcheckable.length); - return ( checkable[0] && !holder.entityName.equals( session.bestGuessEntityName(current) ) ) || - identifierType.isModified(holder.id, getIdentifier(current, session), idcheckable, session); - } - - public String getAssociatedEntityName(SessionFactoryImplementor factory) - throws MappingException { - throw new UnsupportedOperationException("any types do not have a unique referenced persister"); - } - - public boolean[] getPropertyNullability() { - return null; - } - - public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters) - throws MappingException { - throw new UnsupportedOperationException(); - } - - public boolean isReferenceToPrimaryKey() { - return true; - } - - public String getRHSUniqueKeyPropertyName() { - return null; - } - + @Override public String getLHSPropertyName() { return null; } + public boolean isReferenceToPrimaryKey() { + return true; + } + + @Override + public String getRHSUniqueKeyPropertyName() { + return null; + } + + @Override public boolean isAlwaysDirtyChecked() { return false; } + @Override public boolean isEmbeddedInXML() { return false; } - - public boolean[] toColumnNullness(Object value, Mapping mapping) { - boolean[] result = new boolean[ getColumnSpan(mapping) ]; - if (value!=null) Arrays.fill(result, true); - return result; + + @Override + public Joinable getAssociatedJoinable(SessionFactoryImplementor factory) { + throw new UnsupportedOperationException("any types do not have a unique referenced persister"); } - public boolean isDirty(Object old, Object current, boolean[] checkable, SessionImplementor session) - throws HibernateException { - //TODO!!! - return isDirty(old, current, session); + @Override + public String getAssociatedEntityName(SessionFactoryImplementor factory) { + throw new UnsupportedOperationException("any types do not have a unique referenced persister"); } - public boolean isEmbedded() { - return false; + @Override + public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters) { + throw new UnsupportedOperationException(); + } + + + /** + * Used to externalize discrimination per a given identifier. For example, when writing to + * second level cache we write the discrimination resolved concrete type for each entity written. + */ + public static final class ObjectTypeCacheEntry implements Serializable { + final String entityName; + final Serializable id; + + ObjectTypeCacheEntry(String entityName, Serializable id) { + this.entityName = entityName; + this.id = id; + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/MetaType.java b/hibernate-core/src/main/java/org/hibernate/type/MetaType.java index 14bc66d508..fcabf01874 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/MetaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/MetaType.java @@ -45,18 +45,16 @@ import org.hibernate.metamodel.relational.Size; public class MetaType extends AbstractType { public static final String[] REGISTRATION_KEYS = new String[0]; - private final Map values; - private final Map keys; private final Type baseType; + private final Map discriminatorValuesToEntityNameMap; + private final Map entityNameToDiscriminatorValueMap; - public MetaType(Map values, Type baseType) { + public MetaType(Map discriminatorValuesToEntityNameMap, Type baseType) { this.baseType = baseType; - this.values = values; - keys = new HashMap(); - Iterator iter = values.entrySet().iterator(); - while ( iter.hasNext() ) { - Map.Entry me = (Map.Entry) iter.next(); - keys.put( me.getValue(), me.getKey() ); + this.discriminatorValuesToEntityNameMap = discriminatorValuesToEntityNameMap; + this.entityNameToDiscriminatorValueMap = new HashMap(); + for ( Map.Entry entry : discriminatorValuesToEntityNameMap.entrySet() ) { + entityNameToDiscriminatorValueMap.put( entry.getValue(), entry.getKey() ); } } @@ -64,6 +62,10 @@ public class MetaType extends AbstractType { return REGISTRATION_KEYS; } + public Map getDiscriminatorValuesToEntityNameMap() { + return discriminatorValuesToEntityNameMap; + } + public int[] sqlTypes(Mapping mapping) throws MappingException { return baseType.sqlTypes(mapping); } @@ -93,7 +95,7 @@ public class MetaType extends AbstractType { Object owner) throws HibernateException, SQLException { Object key = baseType.nullSafeGet(rs, names, session, owner); - return key==null ? null : values.get(key); + return key==null ? null : discriminatorValuesToEntityNameMap.get(key); } public Object nullSafeGet( @@ -103,12 +105,12 @@ public class MetaType extends AbstractType { Object owner) throws HibernateException, SQLException { Object key = baseType.nullSafeGet(rs, name, session, owner); - return key==null ? null : values.get(key); + return key==null ? null : discriminatorValuesToEntityNameMap.get(key); } public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { - baseType.nullSafeSet(st, value==null ? null : keys.get(value), index, session); + baseType.nullSafeSet(st, value==null ? null : entityNameToDiscriminatorValueMap.get(value), index, session); } public void nullSafeSet( diff --git a/hibernate-core/src/main/java/org/hibernate/type/ObjectType.java b/hibernate-core/src/main/java/org/hibernate/type/ObjectType.java index 4ce68e86a4..83bd3876da 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ObjectType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ObjectType.java @@ -31,9 +31,12 @@ package org.hibernate.type; * @author Steve Ebersole */ public class ObjectType extends AnyType implements BasicType { + /** + * Singleton access + */ public static final ObjectType INSTANCE = new ObjectType(); - public ObjectType() { + private ObjectType() { super( StringType.INSTANCE, SerializableType.INSTANCE ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 60c8427551..0d6606e486 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -518,6 +518,6 @@ public final class TypeFactory implements Serializable { // any type builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Type any(Type metaType, Type identifierType) { - return new AnyType( metaType, identifierType ); + return new AnyType( typeScope, metaType, identifierType ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java index 7c1a12db66..a8285a674c 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeAttributeResultSetProcessorTest.java @@ -23,17 +23,6 @@ */ package org.hibernate.loader; -import java.io.Serializable; -import java.math.BigDecimal; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; - import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; @@ -44,27 +33,42 @@ import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; -import org.junit.Test; - +import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -import org.hibernate.loader.internal.EntityLoadQueryBuilderImpl; -import org.hibernate.loader.internal.LoadQueryAliasResolutionContextImpl; -import org.hibernate.loader.internal.ResultSetProcessorImpl; +import org.hibernate.loader.entity.EntityJoinWalker; +import org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; 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.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.test.component.cascading.toone.PersonalInfo; -import org.hibernate.testing.FailureExpected; +import org.hibernate.persister.entity.OuterJoinLoadable; + +import org.junit.Test; + import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.ExtraAssertions; @@ -132,6 +136,67 @@ public class EncapsulatedCompositeAttributeResultSetProcessorTest extends BaseCo session.close(); } + @Test + public void testNestedCompositeElementCollectionQueryBuilding() { + doCompare( + sessionFactory(), + (OuterJoinLoadable) sessionFactory().getClassMetadata( Customer.class ) + ); + } + + private void doCompare(SessionFactoryImplementor sf, OuterJoinLoadable persister) { + final LoadQueryInfluencers influencers = LoadQueryInfluencers.NONE; + final LockMode lockMode = LockMode.NONE; + final int batchSize = 1; + + final EntityJoinWalker walker = new EntityJoinWalker( + persister, + persister.getKeyColumnNames(), + batchSize, + lockMode, + sf, + influencers + ); + + + SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( sf, influencers ); + LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, persister ); + LoadQueryDetails details = LoadQueryDetails.makeForBatching( + persister.getKeyColumnNames(), + plan, + sf, + new QueryBuildingParameters() { + @Override + public LoadQueryInfluencers getQueryInfluencers() { + return influencers; + } + + @Override + public int getBatchSize() { + return batchSize; + } + + @Override + public LockMode getLockMode() { + return null; + } + + @Override + public LockOptions getLockOptions() { + return null; + } + } + ); + + compare( walker, details ); + } + + private void compare(JoinWalker walker, LoadQueryDetails details) { + System.out.println( "WALKER : " + walker.getSQLString() ); + System.out.println( "LOAD-PLAN : " + details.getSqlStatement() ); + System.out.println(); + } + @Test public void testNestedCompositeElementCollectionProcessing() throws Exception { // create some test data @@ -188,63 +253,60 @@ public class EncapsulatedCompositeAttributeResultSetProcessorTest extends BaseCo } private List getResults(EntityPersister entityPersister ) { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); - final List results = new ArrayList(); + // ultimately, using a LoadPlan requires that it be interpreted into 2 pieces of information: + // 1) The query to execute + // 2) The ResultSetProcessor to use. + // + // Those 2 pieces of information share some common context: + // 1) alias resolution context + // - final Session workSession = openSession(); - workSession.beginTransaction(); - workSession.doWork( - new Work() { - @Override - public void execute(Connection connection) throws SQLException { - PreparedStatement ps = connection.prepareStatement( sql ); - ps.setInt( 1, 1 ); - ResultSet resultSet = ps.executeQuery(); - results.addAll( - resultSetProcessor.extractResults( - NoOpLoadPlanAdvisor.INSTANCE, - resultSet, - (SessionImplementor) workSession, - new QueryParameters(), - new NamedParameterContext() { - @Override - public int[] getNamedParameterLocations(String name) { - return new int[0]; - } - }, - aliasResolutionContext, - true, - false, - null, - null - ) - ); - resultSet.close(); - ps.close(); - } + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory() ); + + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); + final List results = new ArrayList(); + + final Session workSession = openSession(); + workSession.beginTransaction(); + workSession.doWork( + new Work() { + @Override + public void execute(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement( sql ); + ps.setInt( 1, 1 ); + ResultSet resultSet = ps.executeQuery(); + results.addAll( + resultSetProcessor.extractResults( + NoOpLoadPlanAdvisor.INSTANCE, + resultSet, + (SessionImplementor) workSession, + new QueryParameters(), + new NamedParameterContext() { + @Override + public int[] getNamedParameterLocations(String name) { + return new int[0]; + } + }, + aliasResolutionContext, + true, + false, + null, + null + ) + ); + resultSet.close(); + ps.close(); } - ); - workSession.getTransaction().commit(); - workSession.close(); - return results; - } + } + ); + workSession.getTransaction().commit(); + workSession.close(); + return results; + } @Entity( name = "Person" ) public static class Person implements Serializable { @@ -283,7 +345,7 @@ public class EncapsulatedCompositeAttributeResultSetProcessorTest extends BaseCo } @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable( name = "Investments" ) + @CollectionTable( name = "investments", joinColumns = @JoinColumn( name = "customer_id" ) ) public List getInvestments() { return investments; } diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java index 2482fcf4ca..69026877f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EncapsulatedCompositeIdResultSetProcessorTest.java @@ -23,47 +23,40 @@ */ package org.hibernate.loader; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import javax.persistence.Embeddable; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.ManyToOne; - -import org.junit.Test; import org.hibernate.LockOptions; import org.hibernate.Session; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.testing.junit4.ExtraAssertions; import org.hibernate.type.Type; +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.ExtraAssertions; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; /** * @author Gail Badner @@ -193,24 +186,13 @@ public class EncapsulatedCompositeIdResultSetProcessorTest extends BaseCoreFunct } private List getResults(final EntityPersister entityPersister, final Callback callback) { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); + + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); final List results = new ArrayList(); final Session workSession = openSession(); @@ -430,5 +412,28 @@ public class EncapsulatedCompositeIdResultSetProcessorTest extends BaseCoreFunct public void setId(String id) { this.id = id; } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + Card card = (Card) o; + + if ( !id.equals( card.id ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityAssociationResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityAssociationResultSetProcessorTest.java index 33c32e0504..bccdcf5584 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EntityAssociationResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityAssociationResultSetProcessorTest.java @@ -23,38 +23,34 @@ */ package org.hibernate.loader; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; - -import org.junit.Test; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import org.hibernate.Hibernate; import org.hibernate.Session; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; + +import org.junit.Test; + import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.ExtraAssertions; @@ -90,24 +86,12 @@ public class EntityAssociationResultSetProcessorTest extends BaseCoreFunctionalT session.close(); { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); @@ -188,24 +172,12 @@ public class EntityAssociationResultSetProcessorTest extends BaseCoreFunctionalT session.close(); { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java index bd6a8d5a70..5fc9a90b82 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyCollectionResultSetProcessorTest.java @@ -23,46 +23,44 @@ */ package org.hibernate.loader; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.persistence.CollectionTable; -import javax.persistence.ElementCollection; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; - import org.hibernate.Hibernate; import org.hibernate.Session; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; + +import org.junit.Test; + import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.ExtraAssertions; -import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; /** * @author Gail Badner @@ -91,24 +89,12 @@ public class EntityWithNonLazyCollectionResultSetProcessorTest extends BaseCoreF session.close(); { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); @@ -173,7 +159,8 @@ public class EntityWithNonLazyCollectionResultSetProcessorTest extends BaseCoreF private Integer id; private String name; @ElementCollection( fetch = FetchType.EAGER ) - @CollectionTable( name = "NickNames" ) + @CollectionTable( name = "nick_names", joinColumns = @JoinColumn( name = "pid" ) ) + @Column( name = "nick" ) private Set nickNames = new HashSet(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java index abd4dadf7f..a061775a74 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManyListResultSetProcessorTest.java @@ -23,13 +23,6 @@ */ package org.hibernate.loader; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -37,25 +30,28 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; - -import org.junit.Test; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import org.hibernate.Hibernate; import org.hibernate.Session; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; + +import org.junit.Test; + import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.ExtraAssertions; @@ -113,24 +109,12 @@ public class EntityWithNonLazyOneToManyListResultSetProcessorTest extends BaseCo session.close(); { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java index eee117e602..5a84e8a3a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/EntityWithNonLazyOneToManySetResultSetProcessorTest.java @@ -23,15 +23,6 @@ */ package org.hibernate.loader; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -39,25 +30,30 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; - -import org.junit.Test; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.hibernate.Hibernate; import org.hibernate.Session; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; + +import org.junit.Test; + import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.ExtraAssertions; @@ -124,24 +120,12 @@ public class EntityWithNonLazyOneToManySetResultSetProcessorTest extends BaseCor session.close(); { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/loader/Helper.java b/hibernate-core/src/test/java/org/hibernate/loader/Helper.java new file mode 100644 index 0000000000..257b899b15 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/loader/Helper.java @@ -0,0 +1,86 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.plan.exec.query.internal.EntityLoadQueryBuilderImpl; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; +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.persister.entity.EntityPersister; + +/** + * @author Steve Ebersole + */ +public class Helper implements QueryBuildingParameters { + /** + * Singleton access + */ + public static final Helper INSTANCE = new Helper(); + + private Helper() { + } + + public LoadPlan buildLoadPlan(SessionFactoryImplementor sf, EntityPersister entityPersister) { + final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( + sf, + LoadQueryInfluencers.NONE + ); + return LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); + } + + public String generateSql(SessionFactoryImplementor sf, LoadPlan plan, AliasResolutionContext aliasResolutionContext) { + return EntityLoadQueryBuilderImpl.INSTANCE.generateSql( + plan, + sf, + this, + aliasResolutionContext + ); + } + + @Override + public LoadQueryInfluencers getQueryInfluencers() { + return LoadQueryInfluencers.NONE; + } + + @Override + public int getBatchSize() { + return 1; + } + + @Override + public LockMode getLockMode() { + return null; + } + + @Override + public LockOptions getLockOptions() { + return null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/loader/NonEncapsulatedCompositeIdResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/NonEncapsulatedCompositeIdResultSetProcessorTest.java index bd05014d50..81c2833de9 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/NonEncapsulatedCompositeIdResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/NonEncapsulatedCompositeIdResultSetProcessorTest.java @@ -28,24 +28,19 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.Transaction; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.Type; @@ -163,24 +158,12 @@ public class NonEncapsulatedCompositeIdResultSetProcessorTest extends BaseCoreFu } private List getResults(final EntityPersister entityPersister, final Callback callback) { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/loader/SimpleResultSetProcessorTest.java b/hibernate-core/src/test/java/org/hibernate/loader/SimpleResultSetProcessorTest.java index 2256b881c1..e0cdd76818 100644 --- a/hibernate-core/src/test/java/org/hibernate/loader/SimpleResultSetProcessorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/loader/SimpleResultSetProcessorTest.java @@ -25,28 +25,22 @@ package org.hibernate.loader; import javax.persistence.Entity; import javax.persistence.Id; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.hibernate.Session; -import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -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.exec.process.internal.ResultSetProcessorImpl; +import org.hibernate.loader.plan.exec.internal.AliasResolutionContextImpl; +import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; +import org.hibernate.loader.plan.exec.spi.AliasResolutionContext; import org.hibernate.loader.plan.spi.LoadPlan; -import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; -import org.hibernate.loader.spi.LoadQueryAliasResolutionContext; -import org.hibernate.loader.spi.NamedParameterContext; import org.hibernate.loader.spi.NoOpLoadPlanAdvisor; import org.hibernate.persister.entity.EntityPersister; @@ -80,24 +74,12 @@ public class SimpleResultSetProcessorTest extends BaseCoreFunctionalTestCase { session.close(); { - final SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( - sessionFactory(), - LoadQueryInfluencers.NONE - ); - final LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, entityPersister ); - final LoadQueryAliasResolutionContext aliasResolutionContext = - new LoadQueryAliasResolutionContextImpl( - sessionFactory(), - 0, - Collections.singletonMap( plan.getReturns().get( 0 ), new String[] { "abc" } ) - ); - final EntityLoadQueryBuilderImpl queryBuilder = new EntityLoadQueryBuilderImpl( - LoadQueryInfluencers.NONE, - plan - ); - final String sql = queryBuilder.generateSql( 1, sessionFactory(), aliasResolutionContext ); + final LoadPlan plan = Helper.INSTANCE.buildLoadPlan( sessionFactory(), entityPersister ); + final AliasResolutionContext aliasResolutionContext = new AliasResolutionContextImpl( sessionFactory(), 0 ); - final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan ); + final String sql = Helper.INSTANCE.generateSql( sessionFactory(), plan, aliasResolutionContext ); + + final ResultSetProcessorImpl resultSetProcessor = new ResultSetProcessorImpl( plan, true ); final List results = new ArrayList(); final Session workSession = openSession(); @@ -106,6 +88,7 @@ public class SimpleResultSetProcessorTest extends BaseCoreFunctionalTestCase { new Work() { @Override public void execute(Connection connection) throws SQLException { + ( (SessionImplementor) workSession ).getFactory().getJdbcServices().getSqlStatementLogger().logStatement( sql ); PreparedStatement ps = connection.prepareStatement( sql ); ps.setInt( 1, 1 ); ResultSet resultSet = ps.executeQuery(); diff --git a/hibernate-core/src/test/java/org/hibernate/loader/plan/spi/LoadPlanStructureAssertionHelper.java b/hibernate-core/src/test/java/org/hibernate/loader/plan/spi/LoadPlanStructureAssertionHelper.java new file mode 100644 index 0000000000..a828a7a784 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/loader/plan/spi/LoadPlanStructureAssertionHelper.java @@ -0,0 +1,125 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.JoinWalker; +import org.hibernate.loader.entity.EntityJoinWalker; +import org.hibernate.loader.entity.EntityLoader; +import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters; +import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; +import org.hibernate.loader.plan.internal.SingleRootReturnLoadPlanBuilderStrategy; +import org.hibernate.loader.plan.spi.build.LoadPlanBuilder; +import org.hibernate.persister.entity.OuterJoinLoadable; + +/** + * Perform assertions based on a LoadPlan, specifically against the outputs/expectations of the legacy Loader approach. + *

+ * Mainly this is intended to be a transitory set of help since it is expected that Loader will go away replaced by + * LoadPlans, QueryBuilders and ResultSetProcessors. For now I want to make sure that the outputs (e.g., the SQL, + * the extraction aliases) are the same given the same input. That makes sure we have the best possibility of success + * in designing and implementing the "replacement parts". + * + * @author Steve Ebersole + */ +public class LoadPlanStructureAssertionHelper { + /** + * Singleton access to the helper + */ + public static final LoadPlanStructureAssertionHelper INSTANCE = new LoadPlanStructureAssertionHelper(); + + /** + * Performs a basic comparison. Builds a LoadPlan for the given persister and compares it against the + * expectations according to the Loader/Walker corollary. + * + * @param sf The SessionFactory + * @param persister The entity persister for which to build a LoadPlan and compare against the Loader/Walker + * expectations. + */ + public void performBasicComparison(SessionFactoryImplementor sf, OuterJoinLoadable persister) { + // todo : allow these to be passed in by tests? + final LoadQueryInfluencers influencers = LoadQueryInfluencers.NONE; + final LockMode lockMode = LockMode.NONE; + final int batchSize = 1; + + // legacy Loader-based contracts... + final EntityJoinWalker walker = new EntityJoinWalker( + persister, + persister.getKeyColumnNames(), + batchSize, + lockMode, + sf, + influencers + ); +// final EntityLoader loader = new EntityLoader( persister, lockMode, sf, influencers ); + + SingleRootReturnLoadPlanBuilderStrategy strategy = new SingleRootReturnLoadPlanBuilderStrategy( sf, influencers ); + LoadPlan plan = LoadPlanBuilder.buildRootEntityLoadPlan( strategy, persister ); + LoadQueryDetails details = LoadQueryDetails.makeForBatching( + persister.getKeyColumnNames(), + plan, + sf, + new QueryBuildingParameters() { + @Override + public LoadQueryInfluencers getQueryInfluencers() { + return influencers; + } + + @Override + public int getBatchSize() { + return batchSize; + } + + @Override + public LockMode getLockMode() { + return lockMode; + } + + @Override + public LockOptions getLockOptions() { + return null; + } + } + ); + + compare( walker, details ); + } + + private void compare(JoinWalker walker, LoadQueryDetails details) { + System.out.println( "------ SQL -----------------------------------------------------------------" ); + System.out.println( "WALKER : " + walker.getSQLString() ); + System.out.println( "LOAD-PLAN : " + details.getSqlStatement() ); + System.out.println( "----------------------------------------------------------------------------" ); + System.out.println( ); + System.out.println( "------ SUFFIXES ------------------------------------------------------------" ); + System.out.println( "WALKER : " + StringHelper.join( ", ", walker.getSuffixes() ) + " : " + + StringHelper.join( ", ", walker.getCollectionSuffixes() ) ); + System.out.println( "----------------------------------------------------------------------------" ); + System.out.println( ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/loader/plan/spi/LoadPlanStructureAssertionTest.java b/hibernate-core/src/test/java/org/hibernate/loader/plan/spi/LoadPlanStructureAssertionTest.java new file mode 100644 index 0000000000..83d71adc35 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/loader/plan/spi/LoadPlanStructureAssertionTest.java @@ -0,0 +1,86 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.plan.spi; + +import org.hibernate.cfg.Configuration; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.loader.EncapsulatedCompositeIdResultSetProcessorTest; +import org.hibernate.persister.entity.OuterJoinLoadable; + +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseUnitTestCase; + +/** + * Used to assert that "fetch graphs" between JoinWalker and LoadPlan are same. + * + * @author Steve Ebersole + */ +public class LoadPlanStructureAssertionTest extends BaseUnitTestCase { + @Test + public void testJoinedOneToOne() { + // tests the mappings defined in org.hibernate.test.onetoone.joined.JoinedSubclassOneToOneTest + Configuration cfg = new Configuration(); + cfg.addResource( "org/hibernate/test/onetoone/joined/Person.hbm.xml" ); + SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory(); + +// doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.joined.Person.class ) ); + doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.joined.Entity.class ) ); + +// doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.joined.Address.class ) ); + } + + @Test + public void testSpecialOneToOne() { + // tests the mappings defined in org.hibernate.test.onetoone.joined.JoinedSubclassOneToOneTest + Configuration cfg = new Configuration(); + cfg.addResource( "org/hibernate/test/onetoone/formula/Person.hbm.xml" ); + SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory(); + + doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.onetoone.formula.Person.class ) ); + } + + @Test + public void testEncapsulatedCompositeId() { + Configuration cfg = new Configuration(); + cfg.addAnnotatedClass( EncapsulatedCompositeIdResultSetProcessorTest.Parent.class ); + cfg.addAnnotatedClass( EncapsulatedCompositeIdResultSetProcessorTest.CardField.class ); + cfg.addAnnotatedClass( EncapsulatedCompositeIdResultSetProcessorTest.Card.class ); + SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory(); + doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( EncapsulatedCompositeIdResultSetProcessorTest.CardField.class ) ); + } + + @Test + public void testManyToMany() { + Configuration cfg = new Configuration(); + cfg.addResource( "org/hibernate/test/immutable/entitywithmutablecollection/inverse/ContractVariation.hbm.xml" ); + SessionFactoryImplementor sf = (SessionFactoryImplementor) cfg.buildSessionFactory(); + doCompare( sf, (OuterJoinLoadable) sf.getClassMetadata( org.hibernate.test.immutable.entitywithmutablecollection.Contract.class ) ); + + } + + private void doCompare(SessionFactoryImplementor sf, OuterJoinLoadable persister) { + LoadPlanStructureAssertionHelper.INSTANCE.performBasicComparison( sf, persister ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/persister/walking/BasicWalkingTest.java b/hibernate-core/src/test/java/org/hibernate/persister/walking/BasicWalkingTest.java index 90788b3911..5a7ef43eeb 100644 --- a/hibernate-core/src/test/java/org/hibernate/persister/walking/BasicWalkingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/persister/walking/BasicWalkingTest.java @@ -32,6 +32,8 @@ import java.util.List; import org.hibernate.annotations.common.util.StringHelper; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.walking.spi.AnyMappingDefinition; +import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.AssociationVisitationStrategy; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.CollectionDefinition; @@ -207,6 +209,10 @@ public class BasicWalkingTest extends BaseCoreFunctionalTestCase { public void finishingAttribute(AttributeDefinition attributeDefinition) { // nothing to do } + + @Override + public void foundAny(AssociationAttributeDefinition attributeDefinition, AnyMappingDefinition anyDefinition) { + } }, ep ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/CollectionElementTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/CollectionElementTest.java index 0f69d745dd..196173fb91 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/CollectionElementTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/CollectionElementTest.java @@ -120,6 +120,7 @@ public class CollectionElementTest extends BaseCoreFunctionalTestCase { s.clear(); Transaction tx = s.beginTransaction(); boy = (Boy) s.get( Boy.class, boy.getId() ); + assertNotNull( boy ); assertNotNull( boy.getFavoriteToys() ); assertTrue( boy.getFavoriteToys().contains( toy ) ); assertEquals( "@Parent is failing", boy, boy.getFavoriteToys().iterator().next().getOwner() ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/any/AnyTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/any/AnyTypeTest.java index 3c7a4966de..06b93d6f66 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/any/AnyTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/any/AnyTypeTest.java @@ -26,6 +26,8 @@ package org.hibernate.test.any; import org.junit.Test; import org.hibernate.Session; +import org.hibernate.hql.internal.ast.QuerySyntaxException; + import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -70,4 +72,14 @@ public class AnyTypeTest extends BaseCoreFunctionalTestCase { session.getTransaction().commit(); session.close(); } + + @Test( expected = QuerySyntaxException.class ) + public void testJoinFetchOfAnAnyTypeAttribute() { + // Query translator should dis-allow join fetching of an mapping. Let's make sure it does... + Session session = openSession(); + session.beginTransaction(); + session.createQuery( "select p from Person p join fetch p.data" ).list(); + session.getTransaction().commit(); + session.close(); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/lazyonetoone/LazyOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/lazyonetoone/LazyOneToOneTest.java index e70c47e126..e7bb2490a4 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/lazyonetoone/LazyOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/lazyonetoone/LazyOneToOneTest.java @@ -103,7 +103,8 @@ public class LazyOneToOneTest extends BaseCoreFunctionalTestCase { public static class DomainClassesInstrumentedMatcher implements Skip.Matcher { @Override public boolean isMatch() { - return FieldInterceptionHelper.isInstrumented( Person.class ); + // we match (to skip) when the classes are *not* instrumented... + return ! FieldInterceptionHelper.isInstrumented( Person.class ); } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java index 006c6fcaaa..377a6e722e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FooBarTest.java @@ -2842,6 +2842,30 @@ public class FooBarTest extends LegacyTestCase { s.close(); } + @Test + public void loadFoo() { + Session s = openSession(); + s.beginTransaction(); + FooProxy foo = new Foo(); + s.save( foo ); + s.getTransaction().commit(); + s.close(); + + final String id = ( (Foo) foo ).key; + + s = openSession(); + s.beginTransaction(); + foo = (FooProxy) s.load( Foo.class, id ); + s.getTransaction().commit(); + s.close(); + + s = openSession(); + s.beginTransaction(); + s.delete( foo ); + s.getTransaction().commit(); + s.close(); + } + @Test public void testCallback() throws Exception { Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java index a33aa1cca0..04b87537d4 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/QueryCacheTest.java @@ -280,6 +280,7 @@ public class QueryCacheTest extends BaseCoreFunctionalTestCase { sessionFactory().evictQueries(); sessionFactory().getStatistics().clear(); + // persist our 2 items. This saves them to the db, but also into the second level entity cache region Session s = openSession(); Transaction t = s.beginTransaction(); Item i = new Item(); @@ -299,18 +300,24 @@ public class QueryCacheTest extends BaseCoreFunctionalTestCase { Thread.sleep(200); + // perform the cacheable query. this will execute the query (no query cache hit), but the Items will be + // found in second level entity cache region s = openSession(); t = s.beginTransaction(); - List result = s.createQuery( queryString ).setCacheable(true).list(); + List result = s.createQuery( queryString ).setCacheable( true ).list(); assertEquals( result.size(), 2 ); t.commit(); s.close(); - assertEquals( qs.getCacheHitCount(), 0 ); assertEquals( s.getSessionFactory().getStatistics().getEntityFetchCount(), 0 ); + + // evict the Items from the second level entity cache region sessionFactory().evict(Item.class); + // now, perform the cacheable query again. this time we should not execute the query (query cache hit). + // However, the Items will not be found in second level entity cache region this time (we evicted them above) + // nor are they in associated with the session. s = openSession(); t = s.beginTransaction(); result = s.createQuery( queryString ).setCacheable(true).list(); diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties index 51025a3b1e..bcbf2f632b 100644 --- a/hibernate-core/src/test/resources/log4j.properties +++ b/hibernate-core/src/test/resources/log4j.properties @@ -27,17 +27,17 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n #log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (hibernateLoadPlanWalkPath->%X{hibernateLoadPlanWalkPath}) - %m%n -log4j.appender.stdout-mdc=org.apache.log4j.ConsoleAppender -log4j.appender.stdout-mdc.Target=System.out -log4j.appender.stdout-mdc.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout-mdc.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (walk path -> %X{hibernateLoadPlanWalkPath}) - %m%n +#log4j.appender.stdout-mdc=org.apache.log4j.ConsoleAppender +#log4j.appender.stdout-mdc.Target=System.out +#log4j.appender.stdout-mdc.layout=org.apache.log4j.PatternLayout +#log4j.appender.stdout-mdc.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L (walk path -> %X{hibernateLoadPlanWalkPath}) - %m%n log4j.rootLogger=info, stdout -log4j.logger.org.hibernate.loader.plan=trace, stdout-mdc -log4j.additivity.org.hibernate.loader.plan=false -log4j.logger.org.hibernate.persister.walking=trace, stdout-mdc -log4j.additivity.org.hibernate.persister.walking=false +#log4j.logger.org.hibernate.loader.plan=trace, stdout-mdc +#log4j.additivity.org.hibernate.loader.plan=false +#log4j.logger.org.hibernate.persister.walking=trace, stdout-mdc +#log4j.additivity.org.hibernate.persister.walking=false log4j.logger.org.hibernate.tool.hbm2ddl=trace log4j.logger.org.hibernate.testing.cache=debug diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java index 1e65c7a037..e11942e66e 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/AdviceHelper.java @@ -24,6 +24,7 @@ package org.hibernate.jpa.graph.internal.advisor; import org.hibernate.LockMode; +import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; @@ -45,32 +46,33 @@ public class AdviceHelper { } static Fetch buildFetch(FetchOwner fetchOwner, AttributeNodeImplementor attributeNode) { - if ( attributeNode.getAttribute().isAssociation() ) { - if ( attributeNode.getAttribute().isCollection() ) { - return new CollectionFetch( - (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), - LockMode.NONE, - fetchOwner, - new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.SELECT ), - attributeNode.getAttributeName() - ); - } - else { - return new EntityFetch( - (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), - LockMode.NONE, - fetchOwner, - attributeNode.getAttributeName(), - new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.SELECT ) - ); - } - } - else { - return new CompositeFetch( - (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), - fetchOwner, - attributeNode.getAttributeName() - ); - } + throw new NotYetImplementedException(); +// if ( attributeNode.getAttribute().isAssociation() ) { +// if ( attributeNode.getAttribute().isCollection() ) { +// return new CollectionFetch( +// (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), +// LockMode.NONE, +// fetchOwner, +// new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.SELECT ), +// attributeNode.getAttributeName() +// ); +// } +// else { +// return new EntityFetch( +// (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), +// LockMode.NONE, +// fetchOwner, +// attributeNode.getAttributeName(), +// new FetchStrategy( FetchTiming.IMMEDIATE, FetchStyle.SELECT ) +// ); +// } +// } +// else { +// return new CompositeFetch( +// (SessionFactoryImplementor) attributeNode.entityManagerFactory().getSessionFactory(), +// fetchOwner, +// attributeNode.getAttributeName() +// ); +// } } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/ReturnGraphVisitationStrategyImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/ReturnGraphVisitationStrategyImpl.java index fcecaf5697..d85721a271 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/ReturnGraphVisitationStrategyImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/graph/internal/advisor/ReturnGraphVisitationStrategyImpl.java @@ -25,6 +25,7 @@ package org.hibernate.jpa.graph.internal.advisor; import java.util.ArrayDeque; +import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.jpa.graph.internal.EntityGraphImpl; import org.hibernate.loader.plan.spi.CollectionFetch; import org.hibernate.loader.plan.spi.CompositeFetch; @@ -58,11 +59,12 @@ public class ReturnGraphVisitationStrategyImpl extends ReturnGraphVisitationStra @Override public void startingEntityFetch(EntityFetch entityFetch) { - final AdviceNodeDescriptor currentNode = nodeStack.peekFirst(); - final String attributeName = entityFetch.getOwnerPropertyName(); - final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName ); - - nodeStack.addFirst( new AdviceNodeDescriptorEntityReference( entityFetch, fetchedGraphReference ) ); + throw new NotYetImplementedException(); +// final AdviceNodeDescriptor currentNode = nodeStack.peekFirst(); +// final String attributeName = entityFetch.getOwnerPropertyName(); +// final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName ); +// +// nodeStack.addFirst( new AdviceNodeDescriptorEntityReference( entityFetch, fetchedGraphReference ) ); } @Override @@ -72,11 +74,12 @@ public class ReturnGraphVisitationStrategyImpl extends ReturnGraphVisitationStra @Override public void startingCollectionFetch(CollectionFetch collectionFetch) { - final AdviceNodeDescriptor currentNode = nodeStack.peekFirst(); - final String attributeName = collectionFetch.getOwnerPropertyName(); - final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName ); - - nodeStack.addFirst( new AdviceNodeDescriptorCollectionReference( collectionFetch, fetchedGraphReference ) ); + throw new NotYetImplementedException(); +// final AdviceNodeDescriptor currentNode = nodeStack.peekFirst(); +// final String attributeName = collectionFetch.getOwnerPropertyName(); +// final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName ); +// +// nodeStack.addFirst( new AdviceNodeDescriptorCollectionReference( collectionFetch, fetchedGraphReference ) ); } @Override @@ -86,11 +89,12 @@ public class ReturnGraphVisitationStrategyImpl extends ReturnGraphVisitationStra @Override public void startingCompositeFetch(CompositeFetch fetch) { - final AdviceNodeDescriptor currentNode = nodeStack.peekFirst(); - final String attributeName = fetch.getOwnerPropertyName(); - final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName ); - - nodeStack.addFirst( new AdviceNodeDescriptorCompositeReference( fetch, fetchedGraphReference ) ); + throw new NotYetImplementedException(); +// final AdviceNodeDescriptor currentNode = nodeStack.peekFirst(); +// final String attributeName = fetch.getOwnerPropertyName(); +// final JpaGraphReference fetchedGraphReference = currentNode.attributeProcessed( attributeName ); +// +// nodeStack.addFirst( new AdviceNodeDescriptorCompositeReference( fetch, fetchedGraphReference ) ); } @Override diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java index af86c8aeae..fb27820b9e 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/packaging/PackagingTestCase.java @@ -45,7 +45,6 @@ import org.hibernate.jpa.test.Cat; import org.hibernate.jpa.test.Distributor; import org.hibernate.jpa.test.Item; import org.hibernate.jpa.test.Kitten; -import org.hibernate.jpa.test.TestHelper; import org.hibernate.jpa.test.pack.cfgxmlpar.Morito; import org.hibernate.jpa.test.pack.defaultpar.ApplicationServer; import org.hibernate.jpa.test.pack.defaultpar.IncrementListener; @@ -86,17 +85,28 @@ public abstract class PackagingTestCase extends BaseCoreFunctionalTestCase { URL myUrl = originalClassLoader.getResource( PackagingTestCase.class.getName().replace( '.', '/' ) + ".class" ); - int index; - if (myUrl.getFile().contains( "target" )) { + + if ( myUrl == null ) { + fail( "Unable to setup packaging test : could not resolve 'known class' url" ); + } + + int index = -1; + if ( myUrl.getFile().contains( "target" ) ) { // assume there's normally a /target index = myUrl.getFile().lastIndexOf( "target" ); - } else { + } + else if ( myUrl.getFile().contains( "bin" ) ) { // if running in some IDEs, may be in /bin instead index = myUrl.getFile().lastIndexOf( "bin" ); } - - if ( index == -1 ) { - fail( "Unable to setup packaging test" ); + else if ( myUrl.getFile().contains( "out/test" ) ) { + // intellij... intellij sets up project outputs little different + int outIndex = myUrl.getFile().lastIndexOf( "out/test" ); + index = myUrl.getFile().lastIndexOf( '/', outIndex+1 ); + } + + if ( index < 0 ) { + fail( "Unable to setup packaging test : could not interpret url" ); } String baseDirPath = myUrl.getFile().substring( 0, index ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/util/TestPathHelper.java b/hibernate-testing/src/main/java/org/hibernate/testing/util/TestPathHelper.java new file mode 100644 index 0000000000..78ac808ca8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/util/TestPathHelper.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.testing.util; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +import static java.io.File.separatorChar; + +/** + * @author Steve Ebersole + */ +public class TestPathHelper { + /** + * Useful in cases where we need to deal with files/resources in the test compilation output dir of the + * project. This gets a reference to the compilation output directory into which the given class was compiled. + * + * @param knownClass Reference to a Class known to be in the compilation output dir. + * + * @return The root URL + */ + public static URL resolveRootUrl(Class knownClass) { + final String knownClassFileName = '/' + knownClass.getName().replace( '.', separatorChar ) + ".class"; + final URL knownClassFileUrl = knownClass.getResource( knownClassFileName ); + final String knownClassFileUrlString = knownClassFileUrl.toExternalForm(); + + // to start, strip off the class file name + String rootUrlString = knownClassFileUrlString.substring( 0, knownClassFileUrlString.lastIndexOf( separatorChar ) ); + + // then strip off each package dir + final String packageName = knownClass.getPackage().getName(); + for ( String packageNamePart : packageName.split( "\\." ) ) { + rootUrlString = rootUrlString.substring( 0, rootUrlString.lastIndexOf( separatorChar ) ); + } + + try { + return new URL( rootUrlString ); + } + catch (MalformedURLException e) { + throw new RuntimeException( "Could not convert class base url as string to URL ref", e ); + } + } + + /** + * Essentially the same as {@link #resolveRootUrl(Class)}, but here we convert the root URL to a File + * (directory) reference. In fact we delegate to {@link #resolveRootUrl(Class)} and simply convert its + * return into a File reference. + * + * @param knownClass Reference to a Class known to be in the compilation output dir. + * + * @return The root directory + */ + public static File resolveRootDirectory(Class knownClass) { + try { + return new File( resolveRootUrl( knownClass ).toURI() ); + } + catch (URISyntaxException e) { + throw new RuntimeException( "Could not convert class root URL to a URI", e ); + } + } + + /** + * Essentially the same as {@link #resolveRootUrl(Class)}, but here we convert the root URL to a File + * (directory) reference. In fact we delegate to {@link #resolveRootUrl(Class)} and simply convert its + * return into a File reference. + * + * @param knownClass Reference to a Class known to be in the compilation output dir. + * + * @return The root directory + */ + public static File resolveClassFile(Class knownClass) { + final String knownClassFileName = '/' + knownClass.getName().replace( '.', separatorChar ) + ".class"; + final URL knownClassFileUrl = knownClass.getResource( knownClassFileName ); + + try { + return new File( knownClassFileUrl.toURI() ); + } + catch (URISyntaxException e) { + throw new RuntimeException( "Could not convert class root URL to a URI", e ); + } + } + + private TestPathHelper() { + } +}