From fbae6db0abaeb6f050ee97ce53d09d74886a7e47 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 15 Aug 2008 21:20:15 +0000 Subject: [PATCH] HHH-3414 : fetch profiles, phase 1 : join fetching git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@15091 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- core/src/main/java/org/hibernate/Session.java | 36 ++ .../java/org/hibernate/SessionFactory.java | 9 + .../hibernate/UnknownProfileException.java | 48 +++ .../java/org/hibernate/cfg/Configuration.java | 12 + .../java/org/hibernate/cfg/HbmBinder.java | 30 +- .../main/java/org/hibernate/cfg/Mappings.java | 22 +- .../criterion/SubqueryExpression.java | 6 +- .../engine/LoadQueryInfluencers.java | 192 ++++++++++ .../engine/SessionFactoryImplementor.java | 11 +- .../hibernate/engine/SessionImplementor.java | 29 +- .../hibernate/engine/profile/Association.java | 56 +++ .../org/hibernate/engine/profile/Fetch.java | 84 +++++ .../engine/profile/FetchProfile.java | 169 +++++++++ .../hibernate/impl/SessionFactoryImpl.java | 59 ++- .../java/org/hibernate/impl/SessionImpl.java | 200 +++++----- .../hibernate/impl/StatelessSessionImpl.java | 13 +- .../loader/AbstractEntityJoinWalker.java | 94 +++-- .../java/org/hibernate/loader/JoinWalker.java | 354 +++++++++++------- .../org/hibernate/loader/OuterJoinLoader.java | 22 +- .../loader/OuterJoinableAssociation.java | 21 +- .../collection/BasicCollectionJoinWalker.java | 78 ++-- .../collection/BasicCollectionLoader.java | 23 +- .../BatchingCollectionInitializer.java | 35 +- .../collection/CollectionJoinWalker.java | 7 +- .../loader/collection/CollectionLoader.java | 9 +- .../collection/OneToManyJoinWalker.java | 11 +- .../loader/collection/OneToManyLoader.java | 24 +- .../collection/SubselectCollectionLoader.java | 11 +- .../collection/SubselectOneToManyLoader.java | 13 +- .../loader/criteria/CriteriaJoinWalker.java | 64 ++-- .../loader/criteria/CriteriaLoader.java | 9 +- .../loader/entity/AbstractEntityLoader.java | 6 +- .../loader/entity/BatchingEntityLoader.java | 9 +- .../entity/CascadeEntityJoinWalker.java | 3 +- .../loader/entity/CascadeEntityLoader.java | 13 +- .../entity/CollectionElementLoader.java | 10 +- .../loader/entity/EntityJoinWalker.java | 52 ++- .../hibernate/loader/entity/EntityLoader.java | 32 +- .../org/hibernate/mapping/FetchProfile.java | 101 +++++ .../AbstractCollectionPersister.java | 7 +- .../collection/BasicCollectionPersister.java | 9 +- .../collection/OneToManyPersister.java | 10 +- .../entity/AbstractEntityPersister.java | 112 ++++-- .../hibernate/persister/entity/Loadable.java | 8 + .../org/hibernate/hibernate-mapping-3.0.dtd | 23 +- .../org/hibernate/jmx/SessionFactoryStub.java | 4 + .../basic/BasicFetchProfileTest.java | 281 ++++++++++++++ .../test/fetchprofiles/basic/Course.java | 123 ++++++ .../fetchprofiles/basic/CourseOffering.java | 90 +++++ .../test/fetchprofiles/basic/Department.java | 68 ++++ .../test/fetchprofiles/basic/Enrollment.java | 77 ++++ .../test/fetchprofiles/basic/Mappings.hbm.xml | 96 +++++ .../test/fetchprofiles/basic/Student.java | 58 +++ .../src/test/resources/hibernate.properties | 1 + 54 files changed, 2411 insertions(+), 533 deletions(-) create mode 100644 core/src/main/java/org/hibernate/UnknownProfileException.java create mode 100644 core/src/main/java/org/hibernate/engine/LoadQueryInfluencers.java create mode 100644 core/src/main/java/org/hibernate/engine/profile/Association.java create mode 100644 core/src/main/java/org/hibernate/engine/profile/Fetch.java create mode 100644 core/src/main/java/org/hibernate/engine/profile/FetchProfile.java create mode 100644 core/src/main/java/org/hibernate/mapping/FetchProfile.java create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/BasicFetchProfileTest.java create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Course.java create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/CourseOffering.java create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Department.java create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Enrollment.java create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Mappings.hbm.xml create mode 100644 testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Student.java diff --git a/core/src/main/java/org/hibernate/Session.java b/core/src/main/java/org/hibernate/Session.java index 9d881f2193..e697b4f7f6 100644 --- a/core/src/main/java/org/hibernate/Session.java +++ b/core/src/main/java/org/hibernate/Session.java @@ -813,4 +813,40 @@ public interface Session extends Serializable { * @see #disconnect() */ void reconnect(Connection connection) throws HibernateException; + + /** + * Is a particular fetch profile enabled on this session? + * + * @param name The name of the profile to be checked. + * @return True if fetch profile is enabled; false if not. + * @throws UnknownProfileException Indicates that the given name does not + * match any known profile names + * + * @see org.hibernate.engine.profile.FetchProfile for discussion of this feature + */ + public boolean isFetchProfileEnabled(String name) throws UnknownProfileException; + + /** + * Enable a particular fetch profile on this session. No-op if requested + * profile is already enabled. + * + * @param name The name of the fetch profile to be enabled. + * @throws UnknownProfileException Indicates that the given name does not + * match any known profile names + * + * @see org.hibernate.engine.profile.FetchProfile for discussion of this feature + */ + public void enableFetchProfile(String name) throws UnknownProfileException; + + /** + * Disable a particular fetch profile on this session. No-op if requested + * profile is already disabled. + * + * @param name The name of the fetch profile to be disabled. + * @throws UnknownProfileException Indicates that the given name does not + * match any known profile names + * + * @see org.hibernate.engine.profile.FetchProfile for discussion of this feature + */ + public void disableFetchProfile(String name) throws UnknownProfileException; } diff --git a/core/src/main/java/org/hibernate/SessionFactory.java b/core/src/main/java/org/hibernate/SessionFactory.java index 6668b341e2..bc4738a268 100644 --- a/core/src/main/java/org/hibernate/SessionFactory.java +++ b/core/src/main/java/org/hibernate/SessionFactory.java @@ -244,4 +244,13 @@ public interface SessionFactory extends Referenceable, Serializable { * @throws HibernateException If no filter defined with the given name. */ public FilterDefinition getFilterDefinition(String filterName) throws HibernateException; + + /** + * Determine if this session factory contains a fetch profile definition + * registered under the given name. + * + * @param name The name to check + * @return True if there is such a fetch profile; false otherwise. + */ + public boolean containsFetchProfileDefition(String name); } diff --git a/core/src/main/java/org/hibernate/UnknownProfileException.java b/core/src/main/java/org/hibernate/UnknownProfileException.java new file mode 100644 index 0000000000..59291a1dd7 --- /dev/null +++ b/core/src/main/java/org/hibernate/UnknownProfileException.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate; + +/** + * Used to indicate a request against an unknown profile name. + * + * @author Steve Ebersole + */ +public class UnknownProfileException extends HibernateException { + private final String name; + + public UnknownProfileException(String name) { + super( "Unknow fetch profile [" + name + "]" ); + this.name = name; + } + + /** + * The unknown fetch profile name. + * + * @return The unknown fetch profile name. + */ + public String getName() { + return name; + } +} diff --git a/core/src/main/java/org/hibernate/cfg/Configuration.java b/core/src/main/java/org/hibernate/cfg/Configuration.java index 6020e06efe..917542c977 100644 --- a/core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/core/src/main/java/org/hibernate/cfg/Configuration.java @@ -114,6 +114,7 @@ import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; +import org.hibernate.mapping.FetchProfile; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.secure.JACCConfiguration; import org.hibernate.tool.hbm2ddl.DatabaseMetadata; @@ -163,6 +164,7 @@ public class Configuration implements Serializable { */ protected Map sqlResultSetMappings; protected Map filterDefinitions; + protected Map fetchProfiles; protected List secondPasses; protected List propertyReferences; // protected List extendsQueue; @@ -202,6 +204,7 @@ public class Configuration implements Serializable { entityResolver = XMLHelper.DEFAULT_DTD_RESOLVER; eventListeners = new EventListeners(); filterDefinitions = new HashMap(); + fetchProfiles = new HashMap(); // extendsQueue = new ArrayList(); extendsQueue = new HashMap(); auxiliaryDatabaseObjects = new ArrayList(); @@ -720,6 +723,7 @@ public class Configuration implements Serializable { namingStrategy, typeDefs, filterDefinitions, + fetchProfiles, extendsQueue, auxiliaryDatabaseObjects, tableNameBinding, @@ -2181,6 +2185,14 @@ public class Configuration implements Serializable { filterDefinitions.put( definition.getFilterName(), definition ); } + public Map getFetchProfiles() { + return fetchProfiles; + } + + public void addFetchProfile(FetchProfile fetchProfile) { + fetchProfiles.put( fetchProfile.getName(), fetchProfile ); + } + public void addAuxiliaryDatabaseObject(AuxiliaryDatabaseObject object) { auxiliaryDatabaseObjects.add( object ); } diff --git a/core/src/main/java/org/hibernate/cfg/HbmBinder.java b/core/src/main/java/org/hibernate/cfg/HbmBinder.java index 97af09fb04..fd4c691d30 100644 --- a/core/src/main/java/org/hibernate/cfg/HbmBinder.java +++ b/core/src/main/java/org/hibernate/cfg/HbmBinder.java @@ -89,6 +89,7 @@ import org.hibernate.mapping.TypeDef; import org.hibernate.mapping.UnionSubclass; import org.hibernate.mapping.UniqueKey; import org.hibernate.mapping.Value; +import org.hibernate.mapping.FetchProfile; import org.hibernate.persister.entity.JoinedSubclassEntityPersister; import org.hibernate.persister.entity.SingleTableEntityPersister; import org.hibernate.persister.entity.UnionSubclassEntityPersister; @@ -158,6 +159,9 @@ public final class HbmBinder { if ( "filter-def".equals( elementName ) ) { parseFilterDef( element, mappings ); } + else if ( "fetch-profile".equals( elementName ) ) { + parseFetchProfile( element, mappings, null ); + } else if ( "typedef".equals( elementName ) ) { bindTypeDef( element, mappings ); } @@ -546,8 +550,13 @@ public final class HbmBinder { bindDom4jRepresentation( node, persistentClass, mappings, inheritedMetas ); bindMapRepresentation( node, persistentClass, mappings, inheritedMetas ); - bindPersistentClassCommonValues( node, persistentClass, mappings, inheritedMetas ); + Iterator itr = node.elementIterator( "fetch-profile" ); + while ( itr.hasNext() ) { + final Element profileElement = ( Element ) itr.next(); + parseFetchProfile( profileElement, mappings, entityName ); + } + bindPersistentClassCommonValues( node, persistentClass, mappings, inheritedMetas ); } private static void bindPojoRepresentation(Element node, PersistentClass entity, @@ -2963,6 +2972,25 @@ public final class HbmBinder { filterable.addFilter( name, condition ); } + private static void parseFetchProfile(Element element, Mappings mappings, String containingEntityName) { + String profileName = element.attributeValue( "name" ); + FetchProfile profile = mappings.findOrCreateFetchProfile( profileName ); + Iterator itr = element.elementIterator( "fetch" ); + while ( itr.hasNext() ) { + final Element fetchElement = ( Element ) itr.next(); + final String association = fetchElement.attributeValue( "association" ); + final String style = fetchElement.attributeValue( "style" ); + String entityName = fetchElement.attributeValue( "entity" ); + if ( entityName == null ) { + entityName = containingEntityName; + } + if ( entityName == null ) { + throw new MappingException( "could not determine entity for fetch-profile fetch [" + profileName + "]:[" + association + "]" ); + } + profile.addFetch( entityName, association, style ); + } + } + private static String getSubselect(Element element) { String subselect = element.attributeValue( "subselect" ); if ( subselect != null ) { diff --git a/core/src/main/java/org/hibernate/cfg/Mappings.java b/core/src/main/java/org/hibernate/cfg/Mappings.java index 27e531520d..2d458295a9 100644 --- a/core/src/main/java/org/hibernate/cfg/Mappings.java +++ b/core/src/main/java/org/hibernate/cfg/Mappings.java @@ -46,6 +46,7 @@ import org.hibernate.mapping.Table; import org.hibernate.mapping.TypeDef; import org.hibernate.mapping.AuxiliaryDatabaseObject; import org.hibernate.mapping.Column; +import org.hibernate.mapping.FetchProfile; import org.hibernate.util.StringHelper; /** @@ -77,6 +78,7 @@ public class Mappings implements Serializable { protected final List propertyReferences; protected final NamingStrategy namingStrategy; protected final Map filterDefinitions; + protected final Map fetchProfiles; protected final List auxiliaryDatabaseObjects; protected final Map extendsQueue; @@ -111,12 +113,12 @@ public class Mappings implements Serializable { final NamingStrategy namingStrategy, final Map typeDefs, final Map filterDefinitions, + final Map fetchProfiles, // final List extendsQueue, final Map extendsQueue, final List auxiliaryDatabaseObjects, final Map tableNamebinding, - final Map columnNameBindingPerTable - ) { + final Map columnNameBindingPerTable) { this.classes = classes; this.collections = collections; this.queries = queries; @@ -129,6 +131,7 @@ public class Mappings implements Serializable { this.namingStrategy = namingStrategy; this.typeDefs = typeDefs; this.filterDefinitions = filterDefinitions; + this.fetchProfiles = fetchProfiles; this.extendsQueue = extendsQueue; this.auxiliaryDatabaseObjects = auxiliaryDatabaseObjects; this.tableNameBinding = tableNamebinding; @@ -418,7 +421,20 @@ public class Mappings implements Serializable { public FilterDefinition getFilterDefinition(String name) { return (FilterDefinition) filterDefinitions.get(name); } - + + public Map getFetchProfiles() { + return fetchProfiles; + } + + public FetchProfile findOrCreateFetchProfile(String name) { + FetchProfile profile = ( FetchProfile ) fetchProfiles.get( name ); + if ( profile == null ) { + profile = new FetchProfile( name ); + fetchProfiles.put( name, profile ); + } + return profile; + } + public boolean isDefaultLazy() { return defaultLazy; } diff --git a/core/src/main/java/org/hibernate/criterion/SubqueryExpression.java b/core/src/main/java/org/hibernate/criterion/SubqueryExpression.java index 17c93baa7f..5e859a779e 100755 --- a/core/src/main/java/org/hibernate/criterion/SubqueryExpression.java +++ b/core/src/main/java/org/hibernate/criterion/SubqueryExpression.java @@ -32,6 +32,7 @@ import org.hibernate.HibernateException; import org.hibernate.engine.QueryParameters; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.TypedValue; +import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.impl.CriteriaImpl; import org.hibernate.loader.criteria.CriteriaJoinWalker; import org.hibernate.loader.criteria.CriteriaQueryTranslator; @@ -76,8 +77,9 @@ public abstract class SubqueryExpression implements Criterion { factory, criteriaImpl, criteriaImpl.getEntityOrClassName(), - new HashMap(), - innerQuery.getRootSQLALias()); + LoadQueryInfluencers.NONE, + innerQuery.getRootSQLALias() + ); String sql = walker.getSQLString(); diff --git a/core/src/main/java/org/hibernate/engine/LoadQueryInfluencers.java b/core/src/main/java/org/hibernate/engine/LoadQueryInfluencers.java new file mode 100644 index 0000000000..4461db9653 --- /dev/null +++ b/core/src/main/java/org/hibernate/engine/LoadQueryInfluencers.java @@ -0,0 +1,192 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.engine; + +import java.util.Map; +import java.util.Set; +import java.util.Iterator; +import java.util.HashMap; +import java.util.HashSet; + +import org.hibernate.Filter; +import org.hibernate.UnknownProfileException; +import org.hibernate.type.Type; +import org.hibernate.impl.FilterImpl; + +/** + * Centralize all options which can influence the SQL query needed to load and + * entity. Currently such influencers are defined as: + * + * @author Steve Ebersole + */ +public class LoadQueryInfluencers { + /** + * Static reference useful for cases where we are creating load SQL + * outside the context of any influencers. One such example is + * anything created by the session factory. + */ + public static LoadQueryInfluencers NONE = new LoadQueryInfluencers(); + + private final SessionFactoryImplementor sessionFactory; + private String internalFetchProfile; + private Map enabledFilters; + private Set enabledFetchProfileNames; + + public LoadQueryInfluencers() { + this( null, java.util.Collections.EMPTY_MAP, java.util.Collections.EMPTY_SET ); + } + + public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) { + this( sessionFactory, new HashMap(), new HashSet() ); + } + + private LoadQueryInfluencers(SessionFactoryImplementor sessionFactory, Map enabledFilters, Set enabledFetchProfileNames) { + this.sessionFactory = sessionFactory; + this.enabledFilters = enabledFilters; + this.enabledFetchProfileNames = enabledFetchProfileNames; + } + + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + + // internal fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public String getInternalFetchProfile() { + return internalFetchProfile; + } + + public void setInternalFetchProfile(String internalFetchProfile) { + if ( sessionFactory == null ) { + // thats the signal that this is the immutable, context-less + // variety + throw new IllegalStateException( "Cannot modify context-less LoadQueryInfluencers" ); + } + this.internalFetchProfile = internalFetchProfile; + } + + + // filter support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public boolean hasEnabledFilters() { + return enabledFilters != null && !enabledFilters.isEmpty(); + } + + public Map getEnabledFilters() { + // First, validate all the enabled filters... + //TODO: this implementation has bad performance + Iterator itr = enabledFilters.values().iterator(); + while ( itr.hasNext() ) { + final Filter filter = ( Filter ) itr.next(); + filter.validate(); + } + return enabledFilters; + } + + public Filter getEnabledFilter(String filterName) { + return ( Filter ) enabledFilters.get( filterName ); + } + + public Filter enableFilter(String filterName) { + FilterImpl filter = new FilterImpl( sessionFactory.getFilterDefinition( filterName ) ); + enabledFilters.put( filterName, filter ); + return filter; + } + + public void disableFilter(String filterName) { + enabledFilters.remove( filterName ); + } + + public Object getFilterParameterValue(String filterParameterName) { + String[] parsed = parseFilterParameterName( filterParameterName ); + FilterImpl filter = ( FilterImpl ) enabledFilters.get( parsed[0] ); + if ( filter == null ) { + throw new IllegalArgumentException( "Filter [" + parsed[0] + "] currently not enabled" ); + } + return filter.getParameter( parsed[1] ); + } + + public Type getFilterParameterType(String filterParameterName) { + String[] parsed = parseFilterParameterName( filterParameterName ); + FilterDefinition filterDef = sessionFactory.getFilterDefinition( parsed[0] ); + if ( filterDef == null ) { + throw new IllegalArgumentException( "Filter [" + parsed[0] + "] not defined" ); + } + Type type = filterDef.getParameterType( parsed[1] ); + if ( type == null ) { + // this is an internal error of some sort... + throw new InternalError( "Unable to locate type for filter parameter" ); + } + return type; + } + + public static String[] parseFilterParameterName(String filterParameterName) { + int dot = filterParameterName.indexOf( '.' ); + if ( dot <= 0 ) { + throw new IllegalArgumentException( "Invalid filter-parameter name format" ); + } + String filterName = filterParameterName.substring( 0, dot ); + String parameterName = filterParameterName.substring( dot + 1 ); + return new String[] { filterName, parameterName }; + } + + + // fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public boolean hasEnabledFetchProfiles() { + return enabledFetchProfileNames != null && !enabledFetchProfileNames.isEmpty(); + } + + public Set getEnabledFetchProfileNames() { + return enabledFetchProfileNames; + } + + private void checkFetchProfileName(String name) { + if ( !sessionFactory.containsFetchProfileDefition( name ) ) { + throw new UnknownProfileException( name ); + } + } + + public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { + checkFetchProfileName( name ); + return enabledFetchProfileNames.contains( name ); + } + + public void enableFetchProfile(String name) throws UnknownProfileException { + checkFetchProfileName( name ); + enabledFetchProfileNames.add( name ); + } + + public void disableFetchProfile(String name) throws UnknownProfileException { + checkFetchProfileName( name ); + enabledFetchProfileNames.remove( name ); + } + +} diff --git a/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java b/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java index 35eb548d75..5b779226f0 100644 --- a/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java +++ b/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java @@ -37,6 +37,7 @@ import org.hibernate.SessionFactory; import org.hibernate.ConnectionReleaseMode; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.engine.query.QueryPlanCache; +import org.hibernate.engine.profile.FetchProfile; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.cache.QueryCache; @@ -219,5 +220,13 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory { public EntityNotFoundDelegate getEntityNotFoundDelegate(); public SQLFunctionRegistry getSqlFunctionRegistry(); - + + /** + * Retrieve fetch profile by name. + * + * @param name The name of the profile to retrieve. + * @return The profile definition + */ + public FetchProfile getFetchProfile(String name); + } diff --git a/core/src/main/java/org/hibernate/engine/SessionImplementor.java b/core/src/main/java/org/hibernate/engine/SessionImplementor.java index a2d323a619..9d46bd5bbf 100644 --- a/core/src/main/java/org/hibernate/engine/SessionImplementor.java +++ b/core/src/main/java/org/hibernate/engine/SessionImplementor.java @@ -29,6 +29,7 @@ import java.sql.Connection; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.hibernate.CacheMode; import org.hibernate.EntityMode; @@ -237,6 +238,7 @@ public interface SessionImplementor extends Serializable { * @param filterParameterName The filter parameter name in the format * {FILTER_NAME.PARAMETER_NAME}. * @return The filter parameter value. + * @deprecated use #getLoadQueryInfluencers instead */ public Object getFilterParameterValue(String filterParameterName); @@ -245,6 +247,8 @@ public interface SessionImplementor extends Serializable { * * @param filterParameterName The filter parameter name in the format * {FILTER_NAME.PARAMETER_NAME}. + * @return The filter param type + * @deprecated use #getLoadQueryInfluencers instead */ public Type getFilterParameterType(String filterParameterName); @@ -253,6 +257,7 @@ public interface SessionImplementor extends Serializable { * name, with values corresponding to the {@link org.hibernate.impl.FilterImpl} * instance. * @return The currently enabled filters. + * @deprecated use #getLoadQueryInfluencers instead */ public Map getEnabledFilters(); @@ -307,10 +312,22 @@ public interface SessionImplementor extends Serializable { public void afterScrollOperation(); - public void setFetchProfile(String name); - + /** + * Get the internal fetch profile currently associated with this session. + * + * @return The current internal fetch profile, or null if none currently associated. + * @deprecated use #getLoadQueryInfluencers instead + */ public String getFetchProfile(); + /** + * Set the current internal fetch profile for this session. + * + * @param name The internal fetch profile name to use + * @deprecated use #getLoadQueryInfluencers instead + */ + public void setFetchProfile(String name); + public JDBCContext getJDBCContext(); /** @@ -322,4 +339,12 @@ public interface SessionImplementor extends Serializable { * @return True if the session is closed; false otherwise. */ public boolean isClosed(); + + /** + * Get the load query influencers associated with this session. + * + * @return the load query influencers associated with this session; + * should never be null. + */ + public LoadQueryInfluencers getLoadQueryInfluencers(); } diff --git a/core/src/main/java/org/hibernate/engine/profile/Association.java b/core/src/main/java/org/hibernate/engine/profile/Association.java new file mode 100644 index 0000000000..cdd547d10f --- /dev/null +++ b/core/src/main/java/org/hibernate/engine/profile/Association.java @@ -0,0 +1,56 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.engine.profile; + +import org.hibernate.persister.entity.EntityPersister; + +/** + * Models the association of a given fetch. + * + * @author Steve Ebersole + */ +public class Association { + private final EntityPersister owner; + private final String associationPath; + private final String role; + + public Association(EntityPersister owner, String associationPath) { + this.owner = owner; + this.associationPath = associationPath; + this.role = owner.getEntityName() + '.' + associationPath; + } + + public EntityPersister getOwner() { + return owner; + } + + public String getAssociationPath() { + return associationPath; + } + + public String getRole() { + return role; + } +} diff --git a/core/src/main/java/org/hibernate/engine/profile/Fetch.java b/core/src/main/java/org/hibernate/engine/profile/Fetch.java new file mode 100644 index 0000000000..4bd44c3261 --- /dev/null +++ b/core/src/main/java/org/hibernate/engine/profile/Fetch.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.engine.profile; + +/** + * Models an individual fetch within a profile. + * + * @author Steve Ebersole + */ +public class Fetch { + private final Association association; + private final Style style; + + public Fetch(Association association, Style style) { + this.association = association; + this.style = style; + } + + public Association getAssociation() { + return association; + } + + public Style getStyle() { + return style; + } + + /** + * The type or style of fetch. For the moment we limit this to + * join and select, though technically subselect would be valid + * here as as well; however, to support subselect here would + * require major changes to the subselect loading code (which is + * needed for other things as well anyway). + */ + public static class Style { + public static final Style JOIN = new Style( "join" ); + public static final Style SELECT = new Style( "select" ); + + private final String name; + + private Style(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + public static Style parse(String name) { + if ( SELECT.name.equals( name ) ) { + return SELECT; + } + else { + // the default... + return JOIN; + } + } + } + + public String toString() { + return "Fetch[" + style + "{" + association.getRole() + "}]"; + } +} diff --git a/core/src/main/java/org/hibernate/engine/profile/FetchProfile.java b/core/src/main/java/org/hibernate/engine/profile/FetchProfile.java new file mode 100644 index 0000000000..972bcd70c7 --- /dev/null +++ b/core/src/main/java/org/hibernate/engine/profile/FetchProfile.java @@ -0,0 +1,169 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.engine.profile; + +import java.util.Map; +import java.util.HashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.type.Type; +import org.hibernate.type.BagType; +import org.hibernate.type.AssociationType; + +/** + * A 'fetch profile' allows a user to dynamically modify the fetching + * strategy used for particular associations at runtime, whereas that + * information was historically only statically defined in the metadata. + * + * @author Steve Ebersole + */ +public class FetchProfile { + private static final Logger log = LoggerFactory.getLogger( FetchProfile.class ); + + private final String name; + private Map fetches = new HashMap(); + + private boolean containsJoinFetchedCollection = false; + private boolean containsJoinFetchedBag = false; + private Fetch bagJoinFetch; + + /** + * A 'fetch profile' is uniquely named within a + * {@link SessionFactoryImplementor SessionFactory}, thus it is also + * uniquely and easily identifiable within that + * {@link SessionFactoryImplementor SessionFactory}. + * + * @param name The name under which we are bound in the sessionFactory + */ + public FetchProfile(String name) { + this.name = name; + } + + /** + * Add a fetch to the profile. + * + * @param association The association to be fetched + * @param fetchStyleName The name of the fetch style to apply + */ + public void addFetch(Association association, String fetchStyleName) { + addFetch( association, Fetch.Style.parse( fetchStyleName ) ); + } + + /** + * Add a fetch to the profile. + * + * @param association The association to be fetched + * @param style The style to apply + */ + public void addFetch(Association association, Fetch.Style style) { + addFetch( new Fetch( association, style ) ); + } + + /** + * Add a fetch to the profile. + * + * @param fetch The fetch to add. + */ + public void addFetch(Fetch fetch) { + Type associationType = fetch.getAssociation().getOwner().getPropertyType( fetch.getAssociation().getAssociationPath() ); + if ( associationType.isCollectionType() ) { + log.trace( "handling request to add collection fetch [{}]", fetch.getAssociation().getRole() ); + + // couple of things for whcih to account in the case of collection + // join fetches + if ( Fetch.Style.JOIN == fetch.getStyle() ) { + // first, if this is a bag we need to ignore it if we previously + // processed collection join fetches + if ( BagType.class.isInstance( associationType ) ) { + if ( containsJoinFetchedCollection ) { + log.warn( "Ignoring bag join fetch [{}] due to prior collection join fetch", fetch.getAssociation().getRole() ); + return; // EARLY EXIT!!! + } + } + + // also, in cases where we are asked to add a collection join + // fetch where we had already added a bag join fetch previously, + // we need to go back and ignore that previous bag join fetch. + if ( containsJoinFetchedBag ) { + if ( fetches.remove( bagJoinFetch.getAssociation().getRole() ) != bagJoinFetch ) { + // just for safety... + log.warn( "Unable to erase previously added bag join fetch" ); + } + bagJoinFetch = null; + containsJoinFetchedBag = false; + } + + containsJoinFetchedCollection = true; + } + } + fetches.put( fetch.getAssociation().getRole(), fetch ); + } + + /** + * Getter for property 'name'. + * + * @return Value for property 'name'. + */ + public String getName() { + return name; + } + + /** + * Getter for property 'fetches'. Map of {@link Fetch} instances, + * keyed by associaion role + * + * @return Value for property 'fetches'. + */ + public Map getFetches() { + return fetches; + } + + public Fetch getFetchByRole(String role) { + return ( Fetch ) fetches.get( role ); + } + + /** + * Getter for property 'containsJoinFetchedCollection', which flags whether + * this fetch profile contained any collection join fetches. + * + * @return Value for property 'containsJoinFetchedCollection'. + */ + public boolean isContainsJoinFetchedCollection() { + return containsJoinFetchedCollection; + } + + /** + * Getter for property 'containsJoinFetchedBag', which flags whether this + * fetch profile contained any bag join fetches + * + * @return Value for property 'containsJoinFetchedBag'. + */ + public boolean isContainsJoinFetchedBag() { + return containsJoinFetchedBag; + } +} diff --git a/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java b/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java index 6b1e7bf48d..28075ce472 100644 --- a/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java +++ b/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java @@ -84,6 +84,9 @@ import org.hibernate.engine.NamedQueryDefinition; import org.hibernate.engine.NamedSQLQueryDefinition; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.profile.FetchProfile; +import org.hibernate.engine.profile.Fetch; +import org.hibernate.engine.profile.Association; import org.hibernate.engine.query.QueryPlanCache; import org.hibernate.engine.query.sql.NativeSQLQuerySpecification; import org.hibernate.event.EventListeners; @@ -100,6 +103,7 @@ import org.hibernate.persister.PersisterFactory; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Queryable; +import org.hibernate.persister.entity.Loadable; import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.stat.Statistics; @@ -156,6 +160,7 @@ public final class SessionFactoryImpl implements SessionFactory, SessionFactoryI private final transient Map namedSqlQueries; private final transient Map sqlResultSetMappings; private final transient Map filters; + private final transient Map fetchProfiles; private final transient Map imports; private final transient Interceptor interceptor; private final transient Settings settings; @@ -198,6 +203,7 @@ public final class SessionFactoryImpl implements SessionFactory, SessionFactoryI public void sessionFactoryClosed(SessionFactory factory) { } }; + this.filters = new HashMap(); this.filters.putAll( cfg.getFilterDefinitions() ); @@ -412,6 +418,43 @@ public final class SessionFactoryImpl implements SessionFactory, SessionFactoryI } this.entityNotFoundDelegate = entityNotFoundDelegate; + // this needs to happen after persisters are all ready to go... + this.fetchProfiles = new HashMap(); + itr = cfg.getFetchProfiles().values().iterator(); + while ( itr.hasNext() ) { + final org.hibernate.mapping.FetchProfile mappingProfile = + ( org.hibernate.mapping.FetchProfile ) itr.next(); + final FetchProfile fetchProfile = new FetchProfile( mappingProfile.getName() ); + Iterator fetches = mappingProfile.getFetches().iterator(); + while ( fetches.hasNext() ) { + final org.hibernate.mapping.FetchProfile.Fetch mappingFetch = + ( org.hibernate.mapping.FetchProfile.Fetch ) fetches.next(); + // resolve the persister owning the fetch + final String entityName = getImportedClassName( mappingFetch.getEntity() ); + final EntityPersister owner = ( EntityPersister ) ( entityName == null ? null : entityPersisters.get( entityName ) ); + if ( owner == null ) { + throw new HibernateException( + "Unable to resolve entity reference [" + mappingFetch.getEntity() + + "] in fetch profile [" + fetchProfile.getName() + "]" + ); + } + + // validate the specified association fetch + Type associationType = owner.getPropertyType( mappingFetch.getAssociation() ); + if ( associationType == null || !associationType.isAssociationType() ) { + throw new HibernateException( "Fetch profile [" + fetchProfile.getName() + "] specified an invalid association" ); + } + + // resolve the style + final Fetch.Style fetchStyle = Fetch.Style.parse( mappingFetch.getStyle() ); + + // then construct the fetch instance... + fetchProfile.addFetch( new Association( owner, mappingFetch.getAssociation() ), fetchStyle ); + ( ( Loadable ) owner ).registerAffectingFetchProfile( fetchProfile.getName() ); + } + fetchProfiles.put( fetchProfile.getName(), fetchProfile ); + } + this.observer.sessionFactoryCreated( this ); } @@ -1004,6 +1047,10 @@ public final class SessionFactoryImpl implements SessionFactory, SessionFactoryI return def; } + public boolean containsFetchProfileDefition(String name) { + return fetchProfiles.containsKey( name ); + } + public Set getDefinedFilterNames() { return filters.keySet(); } @@ -1061,6 +1108,14 @@ public final class SessionFactoryImpl implements SessionFactory, SessionFactoryI return entityNotFoundDelegate; } + public SQLFunctionRegistry getSqlFunctionRegistry() { + return sqlFunctionRegistry; + } + + public FetchProfile getFetchProfile(String name) { + return ( FetchProfile ) fetchProfiles.get( name ); + } + /** * Custom serialization hook used during Session serialization. * @@ -1102,8 +1157,4 @@ public final class SessionFactoryImpl implements SessionFactory, SessionFactoryI } return ( SessionFactoryImpl ) result; } - - public SQLFunctionRegistry getSqlFunctionRegistry() { - return sqlFunctionRegistry; - } } diff --git a/core/src/main/java/org/hibernate/impl/SessionImpl.java b/core/src/main/java/org/hibernate/impl/SessionImpl.java index c9b0e6b9e7..af1f1e0545 100644 --- a/core/src/main/java/org/hibernate/impl/SessionImpl.java +++ b/core/src/main/java/org/hibernate/impl/SessionImpl.java @@ -66,6 +66,7 @@ import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.TransientObjectException; import org.hibernate.UnresolvableObjectException; +import org.hibernate.UnknownProfileException; import org.hibernate.collection.PersistentCollection; import org.hibernate.engine.ActionQueue; import org.hibernate.engine.CollectionEntry; @@ -76,6 +77,7 @@ import org.hibernate.engine.PersistenceContext; import org.hibernate.engine.QueryParameters; import org.hibernate.engine.StatefulPersistenceContext; import org.hibernate.engine.Status; +import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.engine.query.FilterQueryPlan; import org.hibernate.engine.query.HQLQueryPlan; import org.hibernate.engine.query.NativeSQLQueryPlan; @@ -167,10 +169,8 @@ public final class SessionImpl extends AbstractSessionImpl private transient boolean flushBeforeCompletionEnabled; private transient boolean autoCloseSessionEnabled; private transient ConnectionReleaseMode connectionReleaseMode; - - private transient String fetchProfile; - private transient Map enabledFilters = new HashMap(); + private transient LoadQueryInfluencers loadQueryInfluencers; private transient Session rootSession; private transient Map childSessionsByEntityMode; @@ -195,6 +195,8 @@ public final class SessionImpl extends AbstractSessionImpl this.autoCloseSessionEnabled = false; this.connectionReleaseMode = null; + loadQueryInfluencers = new LoadQueryInfluencers( factory ); + if ( factory.getStatistics().isStatisticsEnabled() ) { factory.getStatisticsImplementor().openSession(); } @@ -239,6 +241,8 @@ public final class SessionImpl extends AbstractSessionImpl this.connectionReleaseMode = connectionReleaseMode; this.jdbcContext = new JDBCContext( this, connection, interceptor ); + loadQueryInfluencers = new LoadQueryInfluencers( factory ); + if ( factory.getStatistics().isStatisticsEnabled() ) { factory.getStatisticsImplementor().openSession(); } @@ -1048,75 +1052,6 @@ public final class SessionImpl extends AbstractSessionImpl flush(); } - public Filter getEnabledFilter(String filterName) { - checkTransactionSynchStatus(); - return (Filter) enabledFilters.get(filterName); - } - - public Filter enableFilter(String filterName) { - errorIfClosed(); - checkTransactionSynchStatus(); - FilterImpl filter = new FilterImpl( factory.getFilterDefinition(filterName) ); - enabledFilters.put(filterName, filter); - return filter; - } - - public void disableFilter(String filterName) { - errorIfClosed(); - checkTransactionSynchStatus(); - enabledFilters.remove(filterName); - } - - public Object getFilterParameterValue(String filterParameterName) { - errorIfClosed(); - checkTransactionSynchStatus(); - String[] parsed = parseFilterParameterName(filterParameterName); - FilterImpl filter = (FilterImpl) enabledFilters.get( parsed[0] ); - if (filter == null) { - throw new IllegalArgumentException("Filter [" + parsed[0] + "] currently not enabled"); - } - return filter.getParameter( parsed[1] ); - } - - public Type getFilterParameterType(String filterParameterName) { - errorIfClosed(); - checkTransactionSynchStatus(); - String[] parsed = parseFilterParameterName(filterParameterName); - FilterDefinition filterDef = factory.getFilterDefinition( parsed[0] ); - if (filterDef == null) { - throw new IllegalArgumentException("Filter [" + parsed[0] + "] not defined"); - } - Type type = filterDef.getParameterType( parsed[1] ); - if (type == null) { - // this is an internal error of some sort... - throw new InternalError("Unable to locate type for filter parameter"); - } - return type; - } - - public Map getEnabledFilters() { - errorIfClosed(); - checkTransactionSynchStatus(); - // First, validate all the enabled filters... - //TODO: this implementation has bad performance - Iterator itr = enabledFilters.values().iterator(); - while ( itr.hasNext() ) { - final Filter filter = (Filter) itr.next(); - filter.validate(); - } - return enabledFilters; - } - - private String[] parseFilterParameterName(String filterParameterName) { - int dot = filterParameterName.indexOf('.'); - if (dot <= 0) { - throw new IllegalArgumentException("Invalid filter-parameter name format"); // TODO: what type? - } - String filterName = filterParameterName.substring(0, dot); - String parameterName = filterParameterName.substring(dot+1); - return new String[] {filterName, parameterName}; - } - /** * Retrieve a list of persistent objects using a hibernate query @@ -1432,7 +1367,7 @@ public final class SessionImpl extends AbstractSessionImpl return listFilter( collection, filter, new QueryParameters( new Type[]{null, type}, new Object[]{null, value} ) ); } - public Collection filter(Object collection, String filter, Object[] values, Type[] types) + public Collection filter(Object collection, String filter, Object[] values, Type[] types) throws HibernateException { Object[] vals = new Object[values.length + 1]; Type[] typs = new Type[types.length + 1]; @@ -1491,7 +1426,7 @@ public final class SessionImpl extends AbstractSessionImpl return plan; } - public List listFilter(Object collection, String filter, QueryParameters queryParameters) + public List listFilter(Object collection, String filter, QueryParameters queryParameters) throws HibernateException { errorIfClosed(); checkTransactionSynchStatus(); @@ -1511,7 +1446,7 @@ public final class SessionImpl extends AbstractSessionImpl return results; } - public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters) + public Iterator iterateFilter(Object collection, String filter, QueryParameters queryParameters) throws HibernateException { errorIfClosed(); checkTransactionSynchStatus(); @@ -1552,7 +1487,7 @@ public final class SessionImpl extends AbstractSessionImpl factory, criteria, entityName, - getEnabledFilters() + getLoadQueryInfluencers() ); autoFlushIfRequired( loader.getQuerySpaces() ); dontFlushFromFind++; @@ -1579,7 +1514,7 @@ public final class SessionImpl extends AbstractSessionImpl factory, criteria, implementors[i], - getEnabledFilters() + getLoadQueryInfluencers() ); spaces.addAll( loaders[i].getQuerySpaces() ); @@ -1680,7 +1615,7 @@ public final class SessionImpl extends AbstractSessionImpl ); } - public ScrollableResults scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) + public ScrollableResults scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) throws HibernateException { errorIfClosed(); checkTransactionSynchStatus(); @@ -1734,7 +1669,7 @@ public final class SessionImpl extends AbstractSessionImpl return factory; } - public void initializeCollection(PersistentCollection collection, boolean writing) + public void initializeCollection(PersistentCollection collection, boolean writing) throws HibernateException { errorIfClosed(); checkTransactionSynchStatus(); @@ -1882,23 +1817,107 @@ public final class SessionImpl extends AbstractSessionImpl // nothing to do in a stateful session } - public String getFetchProfile() { - checkTransactionSynchStatus(); - return fetchProfile; - } - public JDBCContext getJDBCContext() { errorIfClosed(); checkTransactionSynchStatus(); return jdbcContext; } + public LoadQueryInfluencers getLoadQueryInfluencers() { + return loadQueryInfluencers; + } + + // filter support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * {@inheritDoc} + */ + public Filter getEnabledFilter(String filterName) { + checkTransactionSynchStatus(); + return loadQueryInfluencers.getEnabledFilter( filterName ); + } + + /** + * {@inheritDoc} + */ + public Filter enableFilter(String filterName) { + errorIfClosed(); + checkTransactionSynchStatus(); + return loadQueryInfluencers.enableFilter( filterName ); + } + + /** + * {@inheritDoc} + */ + public void disableFilter(String filterName) { + errorIfClosed(); + checkTransactionSynchStatus(); + loadQueryInfluencers.disableFilter( filterName ); + } + + /** + * {@inheritDoc} + */ + public Object getFilterParameterValue(String filterParameterName) { + errorIfClosed(); + checkTransactionSynchStatus(); + return loadQueryInfluencers.getFilterParameterValue( filterParameterName ); + } + + /** + * {@inheritDoc} + */ + public Type getFilterParameterType(String filterParameterName) { + errorIfClosed(); + checkTransactionSynchStatus(); + return loadQueryInfluencers.getFilterParameterType( filterParameterName ); + } + + /** + * {@inheritDoc} + */ + public Map getEnabledFilters() { + errorIfClosed(); + checkTransactionSynchStatus(); + return loadQueryInfluencers.getEnabledFilters(); + } + + + // internal fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * {@inheritDoc} + */ + public String getFetchProfile() { + checkTransactionSynchStatus(); + return loadQueryInfluencers.getInternalFetchProfile(); + } + + /** + * {@inheritDoc} + */ public void setFetchProfile(String fetchProfile) { errorIfClosed(); checkTransactionSynchStatus(); - this.fetchProfile = fetchProfile; + loadQueryInfluencers.setInternalFetchProfile( fetchProfile ); } + + // fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { + return loadQueryInfluencers.isFetchProfileEnabled( name ); + } + + public void enableFetchProfile(String name) throws UnknownProfileException { + loadQueryInfluencers.enableFetchProfile( name ); + } + + public void disableFetchProfile(String name) throws UnknownProfileException { + loadQueryInfluencers.disableFetchProfile( name ); + } + + private void checkTransactionSynchStatus() { if ( jdbcContext != null && !isClosed() ) { jdbcContext.registerSynchronizationIfPossible(); @@ -1923,7 +1942,6 @@ public final class SessionImpl extends AbstractSessionImpl cacheMode = CacheMode.parse( ( String ) ois.readObject() ); flushBeforeCompletionEnabled = ois.readBoolean(); autoCloseSessionEnabled = ois.readBoolean(); - fetchProfile = ( String ) ois.readObject(); interceptor = ( Interceptor ) ois.readObject(); factory = SessionFactoryImpl.deserialize( ois ); @@ -1936,12 +1954,13 @@ public final class SessionImpl extends AbstractSessionImpl persistenceContext = StatefulPersistenceContext.deserialize( ois, this ); actionQueue = ActionQueue.deserialize( ois, this ); - enabledFilters = ( Map ) ois.readObject(); + loadQueryInfluencers = ( LoadQueryInfluencers ) ois.readObject(); + childSessionsByEntityMode = ( Map ) ois.readObject(); - Iterator iter = enabledFilters.values().iterator(); + Iterator iter = loadQueryInfluencers.getEnabledFilters().values().iterator(); while ( iter.hasNext() ) { - ( ( FilterImpl ) iter.next() ).afterDeserialize(factory); + ( ( FilterImpl ) iter.next() ).afterDeserialize( factory ); } if ( isRootSession && childSessionsByEntityMode != null ) { @@ -1975,7 +1994,6 @@ public final class SessionImpl extends AbstractSessionImpl oos.writeObject( cacheMode.toString() ); oos.writeBoolean( flushBeforeCompletionEnabled ); oos.writeBoolean( autoCloseSessionEnabled ); - oos.writeObject( fetchProfile ); // we need to writeObject() on this since interceptor is user defined oos.writeObject( interceptor ); @@ -1989,7 +2007,7 @@ public final class SessionImpl extends AbstractSessionImpl actionQueue.serialize( oos ); // todo : look at optimizing these... - oos.writeObject( enabledFilters ); + oos.writeObject( loadQueryInfluencers ); oos.writeObject( childSessionsByEntityMode ); } } diff --git a/core/src/main/java/org/hibernate/impl/StatelessSessionImpl.java b/core/src/main/java/org/hibernate/impl/StatelessSessionImpl.java index 8bb294d9a2..3b4a039638 100755 --- a/core/src/main/java/org/hibernate/impl/StatelessSessionImpl.java +++ b/core/src/main/java/org/hibernate/impl/StatelessSessionImpl.java @@ -57,6 +57,7 @@ import org.hibernate.engine.PersistenceContext; import org.hibernate.engine.QueryParameters; import org.hibernate.engine.StatefulPersistenceContext; import org.hibernate.engine.Versioning; +import org.hibernate.engine.LoadQueryInfluencers; import org.hibernate.engine.query.HQLQueryPlan; import org.hibernate.engine.query.NativeSQLQueryPlan; import org.hibernate.engine.query.sql.NativeSQLQuerySpecification; @@ -527,12 +528,12 @@ public class StatelessSessionImpl extends AbstractSessionImpl errorIfClosed(); String entityName = criteria.getEntityOrClassName(); CriteriaLoader loader = new CriteriaLoader( - getOuterJoinLoadable(entityName), + getOuterJoinLoadable( entityName ), factory, criteria, entityName, - getEnabledFilters() - ); + getLoadQueryInfluencers() + ); return loader.scroll(this, scrollMode); } @@ -548,7 +549,7 @@ public class StatelessSessionImpl extends AbstractSessionImpl factory, criteria, implementors[i], - getEnabledFilters() + getLoadQueryInfluencers() ); } @@ -623,6 +624,10 @@ public class StatelessSessionImpl extends AbstractSessionImpl return jdbcContext; } + public LoadQueryInfluencers getLoadQueryInfluencers() { + return null; + } + public void setFetchProfile(String name) {} public void afterTransactionBegin(Transaction tx) {} diff --git a/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java b/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java index d429f2a747..1d5af1b347 100755 --- a/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/AbstractEntityJoinWalker.java @@ -26,13 +26,16 @@ package org.hibernate.loader; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.Iterator; import org.hibernate.FetchMode; import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.engine.CascadeStyle; import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.LoadQueryInfluencers; +import org.hibernate.engine.profile.FetchProfile; +import org.hibernate.engine.profile.Fetch; import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.sql.JoinFragment; @@ -51,55 +54,60 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker { private final OuterJoinLoadable persister; private final String alias; - public AbstractEntityJoinWalker(OuterJoinLoadable persister, SessionFactoryImplementor factory, Map enabledFilters) { - this( persister, factory, enabledFilters, null ); + public AbstractEntityJoinWalker( + OuterJoinLoadable persister, + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers) { + this( persister, factory, loadQueryInfluencers, null ); } - public AbstractEntityJoinWalker(OuterJoinLoadable persister, SessionFactoryImplementor factory, Map enabledFilters, String alias) { - super( factory, enabledFilters ); + public AbstractEntityJoinWalker( + OuterJoinLoadable persister, + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers, + String alias) { + super( factory, loadQueryInfluencers ); this.persister = persister; this.alias = ( alias == null ) ? generateRootAlias( persister.getEntityName() ) : alias; } protected final void initAll( - final String whereString, - final String orderByString, - final LockMode lockMode) - throws MappingException { + final String whereString, + final String orderByString, + final LockMode lockMode) throws MappingException { walkEntityTree( persister, getAlias() ); List allAssociations = new ArrayList(); allAssociations.addAll(associations); - allAssociations.add( new OuterJoinableAssociation( - persister.getEntityType(), - null, - null, - alias, - JoinFragment.LEFT_OUTER_JOIN, - getFactory(), - CollectionHelper.EMPTY_MAP - ) ); - + allAssociations.add( + new OuterJoinableAssociation( + persister.getEntityType(), + null, + null, + alias, + JoinFragment.LEFT_OUTER_JOIN, + getFactory(), + CollectionHelper.EMPTY_MAP + ) + ); initPersisters(allAssociations, lockMode); initStatementString( whereString, orderByString, lockMode); } protected final void initProjection( - final String projectionString, - final String whereString, - final String orderByString, - final String groupByString, - final LockMode lockMode) - throws MappingException { + final String projectionString, + final String whereString, + final String orderByString, + final String groupByString, + final LockMode lockMode) throws MappingException { walkEntityTree( persister, getAlias() ); persisters = new Loadable[0]; initStatementString(projectionString, whereString, orderByString, groupByString, lockMode); } private void initStatementString( - final String condition, - final String orderBy, - final LockMode lockMode) - throws MappingException { + final String condition, + final String orderBy, + final LockMode lockMode) throws MappingException { initStatementString(null, condition, orderBy, "", lockMode); } @@ -149,7 +157,33 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker { * The superclass deliberately excludes collections */ protected boolean isJoinedFetchEnabled(AssociationType type, FetchMode config, CascadeStyle cascadeStyle) { - return isJoinedFetchEnabledInMapping(config, type); + return isJoinedFetchEnabledInMapping( config, type ); + } + + protected final boolean isJoinFetchEnabledByProfile(OuterJoinLoadable persister, String path, int propertyNumber) { + if ( !getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { + // perf optimization + return false; + } + + // ugh, this stuff has to be made easier... + String rootPropertyName = persister.getSubclassPropertyName( propertyNumber ); + int pos = path.lastIndexOf( rootPropertyName ); + String relativePropertyPath = pos >= 0 + ? path.substring( pos ) + : rootPropertyName; + String fetchRole = persister.getEntityName() + "." + relativePropertyPath; + + Iterator profiles = getLoadQueryInfluencers().getEnabledFetchProfileNames().iterator(); + while ( profiles.hasNext() ) { + final String profileName = ( String ) profiles.next(); + final FetchProfile profile = getFactory().getFetchProfile( profileName ); + final Fetch fetch = profile.getFetchByRole( fetchRole ); + if ( fetch != null && Fetch.Style.JOIN == fetch.getStyle() ) { + return true; + } + } + return false; } public abstract String getComment(); diff --git a/core/src/main/java/org/hibernate/loader/JoinWalker.java b/core/src/main/java/org/hibernate/loader/JoinWalker.java index 0c977a19f7..b7c45e4959 100755 --- a/core/src/main/java/org/hibernate/loader/JoinWalker.java +++ b/core/src/main/java/org/hibernate/loader/JoinWalker.java @@ -29,7 +29,6 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; import org.hibernate.FetchMode; @@ -39,6 +38,9 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.CascadeStyle; import org.hibernate.engine.JoinHelper; import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.LoadQueryInfluencers; +import org.hibernate.engine.profile.FetchProfile; +import org.hibernate.engine.profile.Fetch; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.EntityPersister; @@ -69,7 +71,7 @@ public class JoinWalker { private final SessionFactoryImplementor factory; protected final List associations = new ArrayList(); private final Set visitedAssociationKeys = new HashSet(); - private final Map enabledFilters; + private final LoadQueryInfluencers loadQueryInfluencers; protected String[] suffixes; protected String[] collectionSuffixes; @@ -81,6 +83,14 @@ public class JoinWalker { protected String[] aliases; protected LockMode[] lockModeArray; protected String sql; + + protected JoinWalker( + SessionFactoryImplementor factory, + LoadQueryInfluencers loadQueryInfluencers) { + this.factory = factory; + this.loadQueryInfluencers = loadQueryInfluencers; + + } public String[] getCollectionSuffixes() { return collectionSuffixes; @@ -169,14 +179,9 @@ public class JoinWalker { protected Dialect getDialect() { return factory.getDialect(); } - - protected Map getEnabledFilters() { - return enabledFilters; - } - protected JoinWalker(SessionFactoryImplementor factory, Map enabledFilters) { - this.factory = factory; - this.enabledFilters = enabledFilters; + public LoadQueryInfluencers getLoadQueryInfluencers() { + return loadQueryInfluencers; } /** @@ -184,15 +189,13 @@ public class JoinWalker { * of associations to be fetched by outerjoin (if necessary) */ private void addAssociationToJoinTreeIfNecessary( - final AssociationType type, - final String[] aliasedLhsColumns, - final String alias, - final String path, - int currentDepth, - final int joinType) - throws MappingException { - - if (joinType>=0) { + final AssociationType type, + final String[] aliasedLhsColumns, + final String alias, + final String path, + int currentDepth, + final int joinType) throws MappingException { + if ( joinType >= 0 ) { addAssociationToJoinTree( type, aliasedLhsColumns, @@ -200,9 +203,8 @@ public class JoinWalker { path, currentDepth, joinType - ); + ); } - } /** @@ -210,35 +212,37 @@ public class JoinWalker { * of associations to be fetched by outerjoin */ private void addAssociationToJoinTree( - final AssociationType type, - final String[] aliasedLhsColumns, - final String alias, - final String path, - final int currentDepth, - final int joinType) - throws MappingException { + final AssociationType type, + final String[] aliasedLhsColumns, + final String alias, + String path, + final int currentDepth, + final int joinType) throws MappingException { Joinable joinable = type.getAssociatedJoinable( getFactory() ); - String subalias = generateTableAlias( - associations.size()+1, //before adding to collection! - path, - joinable - ); + // important to generate alias based on size of association collection + // *before* adding this join to that collection + String subalias = generateTableAlias( associations.size() + 1, path, joinable ); + // NOTE : it should be fine to continue to pass only filters below + // (instead of LoadQueryInfluencers) since "from that point on" we + // only need to worry about restrictions (and not say adding more + // joins) OuterJoinableAssociation assoc = new OuterJoinableAssociation( type, alias, aliasedLhsColumns, subalias, joinType, - getFactory(), - enabledFilters - ); - assoc.validateJoin(path); - associations.add(assoc); + getFactory(), + loadQueryInfluencers.getEnabledFilters() + ); + assoc.validateJoin( path ); + associations.add( assoc ); - int nextDepth = currentDepth+1; + int nextDepth = currentDepth + 1; +// path = ""; if ( !joinable.isCollection() ) { if (joinable instanceof OuterJoinLoadable) { walkEntityTree( @@ -263,11 +267,19 @@ public class JoinWalker { } /** - * For an entity class, return a list of associations to be fetched by outerjoin + * Walk the association tree for an entity, adding associations which should + * be join fetched to the {@link #associations} inst var. This form is the + * entry point into the walking for a given entity, starting the recursive + * calls into {@link #walkEntityTree(OuterJoinLoadable, String, String, int)}. + * + * @param persister The persister representing the entity to be walked. + * @param alias The (root) alias to use for this entity/persister. + * @throws org.hibernate.MappingException ??? */ - protected final void walkEntityTree(OuterJoinLoadable persister, String alias) - throws MappingException { - walkEntityTree(persister, alias, "", 0); + protected final void walkEntityTree( + OuterJoinLoadable persister, + String alias) throws MappingException { + walkEntityTree( persister, alias, "", 0 ); } /** @@ -344,38 +356,49 @@ public class JoinWalker { } /** - * Walk the tree for a particular entity association + * Process a particular association owned by the entity + * + * @param associationType The type representing the association to be + * processed. + * @param persister The owner of the association to be processed. + * @param propertyNumber The property number for the association + * (relative to the persister). + * @param alias The entity alias + * @param path The path to the association + * @param nullable is the association nullable (which I think is supposed + * to indicate inner/outer join semantics). + * @param currentDepth The current join depth + * @throws org.hibernate.MappingException ??? */ - private final void walkEntityAssociationTree( - final AssociationType associationType, - final OuterJoinLoadable persister, - final int propertyNumber, - final String alias, - final String path, - final boolean nullable, - final int currentDepth) - throws MappingException { - + private void walkEntityAssociationTree( + final AssociationType associationType, + final OuterJoinLoadable persister, + final int propertyNumber, + final String alias, + final String path, + final boolean nullable, + final int currentDepth) throws MappingException { String[] aliasedLhsColumns = JoinHelper.getAliasedLHSColumnNames( associationType, alias, propertyNumber, persister, getFactory() - ); - + ); String[] lhsColumns = JoinHelper.getLHSColumnNames( associationType, propertyNumber, persister, getFactory() - ); + ); String lhsTable = JoinHelper.getLHSTableName(associationType, propertyNumber, persister); String subpath = subPath( path, persister.getSubclassPropertyName(propertyNumber) ); int joinType = getJoinType( - associationType, - persister.getFetchMode(propertyNumber), + persister, subpath, + propertyNumber, + associationType, + persister.getFetchMode( propertyNumber ), + persister.getCascadeStyle( propertyNumber ), lhsTable, lhsColumns, nullable, - currentDepth, - persister.getCascadeStyle(propertyNumber) - ); + currentDepth + ); addAssociationToJoinTreeIfNecessary( associationType, aliasedLhsColumns, @@ -383,27 +406,110 @@ public class JoinWalker { subpath, currentDepth, joinType - ); - + ); } /** - * For an entity class, add to a list of associations to be fetched - * by outerjoin + * Determine the appropriate type of join (if any) to use to fetch the + * given association. + * + * @param persister The owner of the association. + * @param path The path to the association + * @param propertyNumber The property number representing the association. + * @param associationType The association type. + * @param metadataFetchMode The metadata-defined fetch mode. + * @param metadataCascadeStyle The metadata-defined cascade style. + * @param lhsTable The owner table + * @param lhsColumns The owner join columns + * @param nullable Is the association nullable. + * @param currentDepth Current join depth + * @return type of join to use ({@link JoinFragment#INNER_JOIN}, + * {@link JoinFragment#LEFT_OUTER_JOIN}, or -1 to indicate no joining. + * @throws MappingException ?? */ - private final void walkEntityTree( - final OuterJoinLoadable persister, - final String alias, - final String path, - final int currentDepth) - throws MappingException { + protected int getJoinType( + OuterJoinLoadable persister, + final String path, + int propertyNumber, + AssociationType associationType, + FetchMode metadataFetchMode, + CascadeStyle metadataCascadeStyle, + String lhsTable, + String[] lhsColumns, + final boolean nullable, + final int currentDepth) throws MappingException { + return getJoinType( + associationType, + metadataFetchMode, + path, + lhsTable, + lhsColumns, + nullable, + currentDepth, + metadataCascadeStyle + ); + } + /** + * Determine the appropriate associationType of join (if any) to use to fetch the + * given association. + * + * @param associationType The association associationType. + * @param config The metadata-defined fetch mode. + * @param path The path to the association + * @param lhsTable The owner table + * @param lhsColumns The owner join columns + * @param nullable Is the association nullable. + * @param currentDepth Current join depth + * @param cascadeStyle The metadata-defined cascade style. + * @return type of join to use ({@link JoinFragment#INNER_JOIN}, + * {@link JoinFragment#LEFT_OUTER_JOIN}, or -1 to indicate no joining. + * @throws MappingException ?? + */ + private int getJoinType( + AssociationType associationType, + FetchMode config, + String path, + String lhsTable, + String[] lhsColumns, + boolean nullable, + int currentDepth, + CascadeStyle cascadeStyle) throws MappingException { + if ( !isJoinedFetchEnabled( associationType, config, cascadeStyle ) ) { + return -1; + } + if ( isTooDeep(currentDepth) || ( associationType.isCollectionType() && isTooManyCollections() ) ) { + return -1; + } + if ( isDuplicateAssociation( lhsTable, lhsColumns, associationType ) ) { + return -1; + } + return getJoinType( nullable, currentDepth ); + } + + /** + * Walk the association tree for an entity, adding associations which should + * be join fetched to the {@link #associations} inst var. This form is the + * entry point into the walking for a given entity, starting the recursive + * calls into {@link #walkEntityTree(OuterJoinLoadable, String, String, int)}. + * + * @param persister The persister representing the entity to be walked. + * @param alias The (root) alias to use for this entity/persister. + * @param path todo this seems to be rooted at the *root* persister + * @param currentDepth The current join depth + * @throws org.hibernate.MappingException ??? + */ + private void walkEntityTree( + final OuterJoinLoadable persister, + final String alias, + final String path, + final int currentDepth) throws MappingException { int n = persister.countSubclassProperties(); - for ( int i=0; i1 ) { + final QueryableCollection persister, + final int maxBatchSize, + final SessionFactoryImplementor factory, + final LoadQueryInfluencers loadQueryInfluencers) throws MappingException { + if ( maxBatchSize > 1 ) { int[] batchSizesToCreate = ArrayHelper.getBatchSizes(maxBatchSize); Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ]; for ( int i=0; i1 ) { + final QueryableCollection persister, + final int maxBatchSize, + final SessionFactoryImplementor factory, + final LoadQueryInfluencers loadQueryInfluencers) throws MappingException { + if ( maxBatchSize > 1 ) { int[] batchSizesToCreate = ArrayHelper.getBatchSizes(maxBatchSize); Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ]; for ( int i=0; i1 ) { int[] batchSizesToCreate = ArrayHelper.getBatchSizes(maxBatchSize); Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ]; for ( int i=0; i READ? - return BatchingEntityLoader.createBatchingEntityLoader( this, batchSize, lockMode, getFactory(), enabledFilters ); + return BatchingEntityLoader.createBatchingEntityLoader( + this, + batchSize, + lockMode, + getFactory(), + loadQueryInfluencers + ); } protected UniqueEntityLoader createEntityLoader(LockMode lockMode) throws MappingException { - return createEntityLoader( lockMode, CollectionHelper.EMPTY_MAP ); + return createEntityLoader( lockMode, LoadQueryInfluencers.NONE ); } protected boolean check(int rows, Serializable id, int tableNumber, Expectation expectation, PreparedStatement statement) throws HibernateException { @@ -3072,21 +3092,49 @@ public abstract class AbstractEntityPersister return loader.load( id, optionalObject, session ); } + public void registerAffectingFetchProfile(String fetchProfileName) { + affectingFetchProfileNames.add( fetchProfileName ); + } + + private boolean isAffectedByEnabledFetchProfiles(SessionImplementor session) { + Iterator itr = session.getLoadQueryInfluencers().getEnabledFetchProfileNames().iterator(); + while ( itr.hasNext() ) { + if ( affectingFetchProfileNames.contains( itr.next() ) ) { + return true; + } + } + return false; + } + + private boolean isAffectedByEnabledFilters(SessionImplementor session) { + return session.getLoadQueryInfluencers().hasEnabledFilters() + && filterHelper.isAffectedBy( session.getLoadQueryInfluencers().getEnabledFilters() ); + } + private UniqueEntityLoader getAppropriateLoader(LockMode lockMode, SessionImplementor session) { - final Map enabledFilters = session.getEnabledFilters(); if ( queryLoader != null ) { + // if the user specified a custom query loader we need to that + // regardless of any other consideration return queryLoader; } - else if ( enabledFilters == null || enabledFilters.isEmpty() ) { - if ( session.getFetchProfile()!=null && LockMode.UPGRADE.greaterThan(lockMode) ) { - return (UniqueEntityLoader) loaders.get( session.getFetchProfile() ); - } - else { - return (UniqueEntityLoader) loaders.get( lockMode ); - } + else if ( isAffectedByEnabledFilters( session ) ) { + // because filters affect the rows returned (because they add + // restirctions) these need to be next in precendence + return createEntityLoader( lockMode, session.getLoadQueryInfluencers() ); + } + else if ( session.getLoadQueryInfluencers().getInternalFetchProfile() != null && LockMode.UPGRADE.greaterThan( lockMode ) ) { + // Next, we consider whether an 'internal' fetch profile has been set. + // This indicates a special fetch profile Hibernate needs applied + // (for its merge loading process e.g.). + return ( UniqueEntityLoader ) loaders.get( session.getLoadQueryInfluencers().getInternalFetchProfile() ); + } + else if ( isAffectedByEnabledFetchProfiles( session ) ) { + // If the session has associated influencers we need to adjust the + // SQL query used for loading based on those influencers + return createEntityLoader( lockMode, session.getLoadQueryInfluencers() ); } else { - return createEntityLoader( lockMode, enabledFilters ); + return ( UniqueEntityLoader ) loaders.get( lockMode ); } } diff --git a/core/src/main/java/org/hibernate/persister/entity/Loadable.java b/core/src/main/java/org/hibernate/persister/entity/Loadable.java index b7298af9d7..515b4a6260 100644 --- a/core/src/main/java/org/hibernate/persister/entity/Loadable.java +++ b/core/src/main/java/org/hibernate/persister/entity/Loadable.java @@ -108,4 +108,12 @@ public interface Loadable extends EntityPersister { public boolean isAbstract(); + /** + * Register the name of a fetch profile determined to have an affect on the + * underlying loadable in regards to the fact that the underlying load SQL + * needs to be adjust when the given fetch profile is enabled. + * + * @param fetchProfileName The name of the profile affecting this. + */ + public void registerAffectingFetchProfile(String fetchProfileName); } diff --git a/core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd b/core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd index 3e32ad410d..bb6c11d109 100644 --- a/core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd +++ b/core/src/main/resources/org/hibernate/hibernate-mapping-3.0.dtd @@ -19,11 +19,12 @@ arbitrary number of queries, and import declarations of arbitrary classes. @@ -78,6 +79,7 @@ arbitrary number of queries, and import declarations of arbitrary classes. ((join*,subclass*)|joined-subclass*|union-subclass*), loader?,sql-insert?,sql-update?,sql-delete?, filter*, + fetch-profile*, resultset*, (query|sql-query)* )> @@ -133,6 +135,22 @@ arbitrary number of queries, and import declarations of arbitrary classes. + + + + + + + + + @@ -231,6 +249,7 @@ application through a property of the Java class. --> join*, subclass*, loader?,sql-insert?,sql-update?,sql-delete?, + fetch-profile*, resultset*, (query|sql-query)* )> @@ -263,6 +282,7 @@ application through a property of the Java class. --> (property|many-to-one|one-to-one|component|dynamic-component|properties|any|map|set|list|bag|idbag|array|primitive-array)*, joined-subclass*, loader?,sql-insert?,sql-update?,sql-delete?, + fetch-profile*, resultset*, (query|sql-query)* )> @@ -298,6 +318,7 @@ application through a property of the Java class. --> (property|many-to-one|one-to-one|component|dynamic-component|properties|any|map|set|list|bag|idbag|array|primitive-array)*, union-subclass*, loader?,sql-insert?,sql-update?,sql-delete?, + fetch-profile*, resultset*, (query|sql-query)* )> diff --git a/jmx/src/main/java/org/hibernate/jmx/SessionFactoryStub.java b/jmx/src/main/java/org/hibernate/jmx/SessionFactoryStub.java index 36e5ac633f..fb6e7cd3b6 100644 --- a/jmx/src/main/java/org/hibernate/jmx/SessionFactoryStub.java +++ b/jmx/src/main/java/org/hibernate/jmx/SessionFactoryStub.java @@ -198,4 +198,8 @@ public class SessionFactoryStub implements SessionFactory { public FilterDefinition getFilterDefinition(String filterName) throws HibernateException { return getImpl().getFilterDefinition( filterName ); } + + public boolean containsFetchProfileDefition(String name) { + return getImpl().containsFetchProfileDefition( name ); + } } diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/BasicFetchProfileTest.java b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/BasicFetchProfileTest.java new file mode 100644 index 0000000000..5864fad98a --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/BasicFetchProfileTest.java @@ -0,0 +1,281 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.test.fetchprofiles.basic; + +import org.hibernate.junit.functional.FunctionalTestCase; +import org.hibernate.Session; +import org.hibernate.Hibernate; +import org.hibernate.UnknownProfileException; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.SessionImplementor; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class BasicFetchProfileTest extends FunctionalTestCase { + public BasicFetchProfileTest(String string) { + super( string ); + } + + public String[] getMappings() { + return new String[] { "fetchprofiles/basic/Mappings.hbm.xml" }; + } + + public String getCacheConcurrencyStrategy() { + return null; + } + + public void configure(Configuration cfg) { + cfg.setProperty( Environment.GENERATE_STATISTICS, "true" ); + } + + private static interface TestData { + public Long getStudentId(); + public Long getDepartmentId(); + public Long getCourseId(); + public Long getSectionId(); + public Long getEnrollmentId(); + } + + private interface TestCode { + public void perform(TestData data); + } + + private void performWithStandardData(TestCode testCode) { + Session session = openSession(); + session.beginTransaction(); + final Department literatureDepartment = new Department( "lit", "Literature" ); + session.save( literatureDepartment ); + final Course lit101 = new Course( new Course.Code( literatureDepartment, 101 ), "Introduction to Literature" ); + session.save( lit101 ); + final CourseOffering section = new CourseOffering( lit101, 1, 2008 ); + session.save( section ); + final Student me = new Student( "Steve" ); + session.save( me ); + final Enrollment enrollment = new Enrollment( section, me ); + section.getEnrollments().add( enrollment ); + session.save( enrollment ); + session.getTransaction().commit(); + session.close(); + + sfi().getStatistics().clear(); + + testCode.perform( + new TestData() { + public Long getStudentId() { + return me.getId(); + } + + public Long getDepartmentId() { + return literatureDepartment.getId(); + } + + public Long getCourseId() { + return lit101.getId(); + } + + public Long getSectionId() { + return section.getId(); + } + + public Long getEnrollmentId() { + return enrollment.getId(); + } + } + ); + + session = openSession(); + session.beginTransaction(); + session.delete( enrollment ); + session.delete( me ); + session.delete( enrollment.getOffering() ); + session.delete( enrollment.getOffering().getCourse() ); + session.delete( enrollment.getOffering().getCourse().getCode().getDepartment() ); + session.getTransaction().commit(); + session.close(); + } + + public void testNormalLoading() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + CourseOffering section = ( CourseOffering ) session.get( CourseOffering.class, data.getSectionId() ); + assertEquals( 1, sfi().getStatistics().getEntityLoadCount() ); + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertFalse( Hibernate.isInitialized( section.getCourse() ) ); + assertFalse( Hibernate.isInitialized( section.getEnrollments() ) ); + assertFalse( Hibernate.isInitialized( section.getCourse().getCode().getDepartment() ) ); + assertTrue( Hibernate.isInitialized( section.getCourse() ) ); + assertEquals( 1, sfi().getStatistics().getEntityFetchCount() ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } + + public void testNormalCriteria() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + CourseOffering section = ( CourseOffering ) session.createCriteria( CourseOffering.class ).uniqueResult(); + assertEquals( 1, sfi().getStatistics().getEntityLoadCount() ); + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertFalse( Hibernate.isInitialized( section.getCourse() ) ); + assertFalse( Hibernate.isInitialized( section.getEnrollments() ) ); + assertFalse( Hibernate.isInitialized( section.getCourse().getCode().getDepartment() ) ); + assertTrue( Hibernate.isInitialized( section.getCourse() ) ); + assertEquals( 1, sfi().getStatistics().getEntityFetchCount() ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } + + public void testBasicFetchProfileOperation() { + assertTrue( "fetch profile not parsed properly", sfi().containsFetchProfileDefition( "enrollment.details" ) ); + assertTrue( "fetch profile not parsed properly", sfi().containsFetchProfileDefition( "offering.details" ) ); + assertTrue( "fetch profile not parsed properly", sfi().containsFetchProfileDefition( "course.details" ) ); + Session s = openSession(); + SessionImplementor si = ( SessionImplementor ) s; + s.enableFetchProfile( "enrollment.details" ); + assertTrue( si.getLoadQueryInfluencers().hasEnabledFetchProfiles() ); + s.disableFetchProfile( "enrollment.details" ); + assertFalse( si.getLoadQueryInfluencers().hasEnabledFetchProfiles() ); + try { + s.enableFetchProfile( "never-gonna-get-it" ); + fail( "expecting failure on undefined fetch-profile" ); + } + catch ( UnknownProfileException expected ) { + } + s.close(); + } + + public void testLoadManyToOneFetchProfile() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + session.enableFetchProfile( "enrollment.details" ); + Enrollment enrollment = ( Enrollment ) session.get( Enrollment.class, data.getEnrollmentId() ); + assertEquals( 3, sfi().getStatistics().getEntityLoadCount() ); // enrollment + (section + student) + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertTrue( Hibernate.isInitialized( enrollment.getOffering() ) ); + assertTrue( Hibernate.isInitialized( enrollment.getStudent() ) ); + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } + + public void testCriteriaManyToOneFetchProfile() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + session.enableFetchProfile( "enrollment.details" ); + Enrollment enrollment = ( Enrollment ) session.createCriteria( Enrollment.class ).uniqueResult(); + assertEquals( 3, sfi().getStatistics().getEntityLoadCount() ); // enrollment + (section + student) + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertTrue( Hibernate.isInitialized( enrollment.getOffering() ) ); + assertTrue( Hibernate.isInitialized( enrollment.getStudent() ) ); + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } + + public void testLoadOneToManyFetchProfile() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + session.enableFetchProfile( "offering.details" ); + CourseOffering section = ( CourseOffering ) session.get( CourseOffering.class, data.getSectionId() ); + assertEquals( 3, sfi().getStatistics().getEntityLoadCount() ); // section + (enrollments + course) + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertTrue( Hibernate.isInitialized( section.getEnrollments() ) ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } + + public void testLoadDeepFetchProfile() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + // enable both enrollment and offering detail profiles; + // then loading the section/offering should fetch the enrollment + // which in turn should fetch student (+ offering). + session.enableFetchProfile( "offering.details" ); + session.enableFetchProfile( "enrollment.details" ); + CourseOffering section = ( CourseOffering ) session.get( CourseOffering.class, data.getSectionId() ); + assertEquals( 4, sfi().getStatistics().getEntityLoadCount() ); // section + (course + enrollments + (student)) + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertTrue( Hibernate.isInitialized( section.getEnrollments() ) ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } + + public void testLoadComponentDerefFetchProfile() { + performWithStandardData( + new TestCode() { + public void perform(TestData data) { + Session session = openSession(); + session.beginTransaction(); + session.enableFetchProfile( "course.details" ); + Course course = ( Course ) session.get( Course.class, data.getCourseId() ); + assertEquals( 2, sfi().getStatistics().getEntityLoadCount() ); // course + department + assertEquals( 0, sfi().getStatistics().getEntityFetchCount() ); + assertTrue( Hibernate.isInitialized( course.getCode().getDepartment() ) ); + session.getTransaction().commit(); + session.close(); + } + } + ); + } +} diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Course.java b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Course.java new file mode 100644 index 0000000000..3f6333647d --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Course.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.test.fetchprofiles.basic; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class Course { + private Long id; + private Code code; + private String name; + + public Course() { + } + + public Course(Code code, String name) { + this.code = code; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Code getCode() { + return code; + } + + public void setCode(Code code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public static class Code { + private Department department; + private int number; + + public Code() { + } + + public Code(Department department, int number) { + this.department = department; + this.number = number; + } + + public Department getDepartment() { + return department; + } + + public void setDepartment(Department department) { + this.department = department; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof Code ) ) { + return false; + } + + Code code = ( Code ) o; + + if ( number != code.number ) { + return false; + } + if ( !department.equals( code.department ) ) { + return false; + } + + return true; + } + + public int hashCode() { + int result; + result = department.hashCode(); + result = 31 * result + number; + return result; + } + } +} diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/CourseOffering.java b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/CourseOffering.java new file mode 100644 index 0000000000..2e5fa41a0e --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/CourseOffering.java @@ -0,0 +1,90 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.test.fetchprofiles.basic; + +import java.util.Set; +import java.util.HashSet; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class CourseOffering { + private Long id; + private Course course; + private int semester; + private int year; + private Set enrollments = new HashSet(); + + public CourseOffering() { + } + + public CourseOffering(Course course, int semester, int year) { + this.course = course; + this.semester = semester; + this.year = year; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Course getCourse() { + return course; + } + + public void setCourse(Course course) { + this.course = course; + } + + public int getSemester() { + return semester; + } + + public void setSemester(int semester) { + this.semester = semester; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public Set getEnrollments() { + return enrollments; + } + + public void setEnrollments(Set enrollments) { + this.enrollments = enrollments; + } +} diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Department.java b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Department.java new file mode 100644 index 0000000000..0f8429143a --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Department.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.test.fetchprofiles.basic; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class Department { + private Long id; + private String code; + private String name; + + public Department() { + } + + public Department(String code, String name) { + this.code = code; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Enrollment.java b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Enrollment.java new file mode 100644 index 0000000000..578d771036 --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Enrollment.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.test.fetchprofiles.basic; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class Enrollment { + private Long id; + private CourseOffering offering; + private Student student; + private int finalGrade; + + public Enrollment() { + } + + public Enrollment(CourseOffering offering, Student student) { + this.offering = offering; + this.student = student; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public CourseOffering getOffering() { + return offering; + } + + public void setOffering(CourseOffering offering) { + this.offering = offering; + } + + public Student getStudent() { + return student; + } + + public void setStudent(Student student) { + this.student = student; + } + + public int getFinalGrade() { + return finalGrade; + } + + public void setFinalGrade(int finalGrade) { + this.finalGrade = finalGrade; + } +} diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Mappings.hbm.xml b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Mappings.hbm.xml new file mode 100644 index 0000000000..4cdaf9cf3c --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Mappings.hbm.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Student.java b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Student.java new file mode 100644 index 0000000000..06ab0426de --- /dev/null +++ b/testsuite/src/test/java/org/hibernate/test/fetchprofiles/basic/Student.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Middleware LLC. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + * + */ +package org.hibernate.test.fetchprofiles.basic; + +/** + * TODO : javadoc + * + * @author Steve Ebersole + */ +public class Student { + private Long id; + private String name; + + public Student() { + } + + public Student(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/testsuite/src/test/resources/hibernate.properties b/testsuite/src/test/resources/hibernate.properties index be95e86067..b6eeb1a820 100644 --- a/testsuite/src/test/resources/hibernate.properties +++ b/testsuite/src/test/resources/hibernate.properties @@ -22,6 +22,7 @@ hibernate.connection.isolation ${jdbc.isolation} hibernate.connection.pool_size 5 +hibernate.show_sql true hibernate.format_sql true hibernate.max_fetch_depth 5