HHH-16651 support all fetch styles in fetch profiles
Finally, after all these years!
This commit is contained in:
parent
4795b94f68
commit
a5ae1a479a
|
@ -22,7 +22,6 @@ import org.hibernate.annotations.ConverterRegistration;
|
|||
import org.hibernate.annotations.ConverterRegistrations;
|
||||
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
|
||||
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
|
||||
import org.hibernate.annotations.FetchMode;
|
||||
import org.hibernate.annotations.FetchProfile;
|
||||
import org.hibernate.annotations.FetchProfile.FetchOverride;
|
||||
import org.hibernate.annotations.FetchProfiles;
|
||||
|
@ -853,9 +852,7 @@ public final class AnnotationBinder {
|
|||
final String name = fetchProfile.name();
|
||||
if ( reuseOrCreateFetchProfile( context, name ) ) {
|
||||
for ( FetchOverride fetch : fetchProfile.fetchOverrides() ) {
|
||||
if ( fetch.mode() != FetchMode.JOIN ) {
|
||||
throw new MappingException( "Only 'FetchMode.JOIN' is currently supported" );
|
||||
}
|
||||
// TODO: validate which modes are valid where
|
||||
context.getMetadataCollector()
|
||||
.addSecondPass( new FetchOverrideSecondPass( name, fetch, context ) );
|
||||
}
|
||||
|
|
|
@ -1486,6 +1486,10 @@ public abstract class CollectionBinder {
|
|||
setHibernateFetchMode( fetch.value() );
|
||||
result = true;
|
||||
}
|
||||
else {
|
||||
buildingContext.getMetadataCollector()
|
||||
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, propertyName, buildingContext ) );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -361,6 +361,11 @@ public class ToOneBinder {
|
|||
setHibernateFetchMode( toOne, property, fetch.value() );
|
||||
result = true;
|
||||
}
|
||||
else {
|
||||
final MetadataBuildingContext context = toOne.getBuildingContext();
|
||||
context.getMetadataCollector()
|
||||
.addSecondPass( new FetchSecondPass( fetch, propertyHolder, inferredData.getPropertyName(), context ) );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.engine.profile;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Models an individual fetch override within a profile.
|
||||
*
|
||||
|
@ -35,31 +37,25 @@ public class Fetch {
|
|||
}
|
||||
|
||||
/**
|
||||
* The type or style of fetch. For the moment we limit this to
|
||||
* join and select, though technically subselect would be valid
|
||||
* here 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).
|
||||
* The type or style of fetch.
|
||||
*/
|
||||
public enum Style {
|
||||
/**
|
||||
* Fetch via a join
|
||||
*/
|
||||
JOIN( "join" ),
|
||||
JOIN,
|
||||
/**
|
||||
* Fetch via a subsequent select
|
||||
*/
|
||||
SELECT( "select" );
|
||||
|
||||
private final String name;
|
||||
|
||||
Style(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
SELECT,
|
||||
/**
|
||||
* Fetch via a subsequent subselect
|
||||
*/
|
||||
SUBSELECT;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,13 +66,12 @@ public class Fetch {
|
|||
* @return The style; {@link #JOIN} is returned if not recognized
|
||||
*/
|
||||
public static Style parse(String name) {
|
||||
if ( SELECT.name.equals( name ) ) {
|
||||
return SELECT;
|
||||
}
|
||||
else {
|
||||
// the default...
|
||||
return JOIN;
|
||||
for ( Style style: values() ) {
|
||||
if ( style.name().equalsIgnoreCase( name ) ) {
|
||||
return style;
|
||||
}
|
||||
}
|
||||
return JOIN;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -167,4 +167,9 @@ public class FetchProfile {
|
|||
public boolean isContainsJoinFetchedBag() {
|
||||
return containsJoinFetchedBag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FetchProfile[" + name + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,17 @@ import java.util.function.Supplier;
|
|||
|
||||
import org.hibernate.Filter;
|
||||
import org.hibernate.UnknownProfileException;
|
||||
import org.hibernate.engine.profile.Fetch;
|
||||
import org.hibernate.engine.profile.FetchProfile;
|
||||
import org.hibernate.internal.FilterImpl;
|
||||
import org.hibernate.internal.SessionCreationOptions;
|
||||
import org.hibernate.loader.ast.spi.CascadingFetchProfile;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static org.hibernate.engine.profile.Fetch.Style.SUBSELECT;
|
||||
|
||||
/**
|
||||
* Centralize all options which can influence the SQL query needed to load an
|
||||
* entity. Currently, such influencers are defined as:<ul>
|
||||
|
@ -157,7 +162,7 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
*/
|
||||
public Set<String> getEnabledFilterNames() {
|
||||
if ( enabledFilters == null ) {
|
||||
return Collections.emptySet();
|
||||
return emptySet();
|
||||
}
|
||||
else {
|
||||
return Collections.unmodifiableSet( enabledFilters.keySet() );
|
||||
|
@ -221,7 +226,7 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
}
|
||||
|
||||
public Set<String> getEnabledFetchProfileNames() {
|
||||
return Objects.requireNonNullElse( enabledFetchProfileNames, Collections.emptySet() );
|
||||
return Objects.requireNonNullElse( enabledFetchProfileNames, emptySet() );
|
||||
}
|
||||
|
||||
private void checkFetchProfileName(String name) {
|
||||
|
@ -302,7 +307,24 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
}
|
||||
|
||||
public boolean effectiveSubselectFetchEnabled(CollectionPersister persister) {
|
||||
return subselectFetchEnabled || persister.isSubselectLoadable();
|
||||
return subselectFetchEnabled
|
||||
|| persister.isSubselectLoadable()
|
||||
|| isSubselectFetchEnabledInProfile( persister );
|
||||
}
|
||||
|
||||
private boolean isSubselectFetchEnabledInProfile(CollectionPersister persister) {
|
||||
if ( hasEnabledFetchProfiles() ) {
|
||||
for ( String profile : getEnabledFetchProfileNames() ) {
|
||||
final FetchProfile fetchProfile = sessionFactory.getFetchProfile( profile );
|
||||
if ( fetchProfile != null ) {
|
||||
final Fetch fetch = fetchProfile.getFetchByRole( persister.getRole() );
|
||||
if ( fetch != null && fetch.getStyle() == SUBSELECT ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkMutability() {
|
||||
|
@ -315,6 +337,23 @@ public class LoadQueryInfluencers implements Serializable {
|
|||
|
||||
public boolean hasSubselectLoadableCollections(EntityPersister persister) {
|
||||
return persister.hasSubselectLoadableCollections()
|
||||
|| subselectFetchEnabled && persister.hasCollections();
|
||||
|| subselectFetchEnabled && persister.hasCollections()
|
||||
|| hasSubselectLoadableCollectionsEnabledInProfile( persister );
|
||||
}
|
||||
|
||||
private boolean hasSubselectLoadableCollectionsEnabledInProfile(EntityPersister persister) {
|
||||
if ( hasEnabledFetchProfiles() ) {
|
||||
for ( String profile : getEnabledFetchProfileNames() ) {
|
||||
final FetchProfile fetchProfile = sessionFactory.getFetchProfile( profile );
|
||||
for ( Fetch fetch : fetchProfile.getFetches().values() ) {
|
||||
// TODO: check that it's relevant to this persister??
|
||||
if ( fetch.getStyle() == SUBSELECT ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
|
|
@ -88,6 +88,7 @@ import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl;
|
|||
import org.jboss.logging.Logger;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.hibernate.engine.profile.Fetch.Style;
|
||||
import static org.hibernate.query.results.ResultsHelper.attributeName;
|
||||
|
||||
/**
|
||||
|
@ -553,7 +554,7 @@ public class LoaderSelectBuilder {
|
|||
}
|
||||
|
||||
private LoaderSqlAstCreationState createSqlAstCreationState(QuerySpec rootQuerySpec) {
|
||||
final LoaderSqlAstCreationState sqlAstCreationState = new LoaderSqlAstCreationState(
|
||||
return new LoaderSqlAstCreationState(
|
||||
rootQuerySpec,
|
||||
new SqlAliasBaseManager(),
|
||||
new SimpleFromClauseAccessImpl(),
|
||||
|
@ -563,7 +564,6 @@ public class LoaderSelectBuilder {
|
|||
loadQueryInfluencers,
|
||||
creationContext
|
||||
);
|
||||
return sqlAstCreationState;
|
||||
}
|
||||
|
||||
private void applyRestriction(
|
||||
|
@ -875,31 +875,25 @@ public class LoaderSelectBuilder {
|
|||
for ( String enabledFetchProfileName : loadQueryInfluencers.getEnabledFetchProfileNames() ) {
|
||||
final FetchProfile enabledFetchProfile = creationContext.getSessionFactory()
|
||||
.getFetchProfile( enabledFetchProfileName );
|
||||
final org.hibernate.engine.profile.Fetch profileFetch = enabledFetchProfile.getFetchByRole(
|
||||
fetchableRole );
|
||||
|
||||
final org.hibernate.engine.profile.Fetch profileFetch =
|
||||
enabledFetchProfile.getFetchByRole( fetchableRole );
|
||||
if ( profileFetch != null ) {
|
||||
fetchTiming = FetchTiming.IMMEDIATE;
|
||||
joined = joined || profileFetch.getStyle() == org.hibernate.engine.profile.Fetch.Style.JOIN;
|
||||
joined = joined || profileFetch.getStyle() == Style.JOIN;
|
||||
explicitFetch = shouldExplicitFetch( maximumFetchDepth, fetchable, creationState );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( loadQueryInfluencers.getEnabledCascadingFetchProfile() != null ) {
|
||||
final CascadeStyle cascadeStyle = fetchable.asAttributeMapping().getAttributeMetadata()
|
||||
.getCascadeStyle();
|
||||
final CascadingAction<?> cascadingAction = loadQueryInfluencers.getEnabledCascadingFetchProfile()
|
||||
.getCascadingAction();
|
||||
final CascadeStyle cascadeStyle = fetchable.
|
||||
asAttributeMapping().getAttributeMetadata().getCascadeStyle();
|
||||
final CascadingAction<?> cascadingAction =
|
||||
loadQueryInfluencers.getEnabledCascadingFetchProfile().getCascadingAction();
|
||||
if ( cascadeStyle == null || cascadeStyle.doCascade( cascadingAction ) ) {
|
||||
fetchTiming = FetchTiming.IMMEDIATE;
|
||||
// In 5.x the CascadeEntityJoinWalker only join fetched the first collection fetch
|
||||
if ( isFetchablePluralAttributeMapping ) {
|
||||
joined = rowCardinality == RowCardinality.SINGLE;
|
||||
}
|
||||
else {
|
||||
joined = true;
|
||||
}
|
||||
joined = !isFetchablePluralAttributeMapping || rowCardinality == RowCardinality.SINGLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import jakarta.persistence.GeneratedValue;
|
|||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.annotations.Fetch;
|
||||
import org.hibernate.annotations.FetchProfile;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
|
@ -13,10 +12,14 @@ import org.hibernate.testing.orm.junit.SessionFactory;
|
|||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static jakarta.persistence.FetchType.LAZY;
|
||||
import static org.hibernate.Hibernate.isInitialized;
|
||||
import static org.hibernate.annotations.FetchMode.JOIN;
|
||||
import static org.hibernate.annotations.FetchMode.SELECT;
|
||||
import static org.hibernate.annotations.FetchMode.SUBSELECT;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
@ -25,10 +28,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
@FetchProfile(name = NewFetchTest.NEW_PROFILE)
|
||||
@FetchProfile(name = NewFetchTest.OLD_PROFILE,
|
||||
fetchOverrides = @FetchProfile.FetchOverride(entity = NewFetchTest.E.class, association = "f"))
|
||||
@FetchProfile(name = NewFetchTest.SUBSELECT_PROFILE)
|
||||
@FetchProfile(name = NewFetchTest.SELECT_PROFILE)
|
||||
@FetchProfile(name = NewFetchTest.JOIN_PROFILE)
|
||||
@FetchProfile(name = NewFetchTest.OLD_SUBSELECT_PROFILE,
|
||||
fetchOverrides = @FetchProfile.FetchOverride(entity = NewFetchTest.F.class, association = "es", mode = SUBSELECT))
|
||||
public class NewFetchTest {
|
||||
|
||||
public static final String NEW_PROFILE = "new-profile";
|
||||
public static final String OLD_PROFILE = "old-profile";
|
||||
static final String NEW_PROFILE = "new-profile";
|
||||
static final String OLD_PROFILE = "old-profile";
|
||||
static final String SUBSELECT_PROFILE = "subselect-profile";
|
||||
static final String SELECT_PROFILE = "select-profile";
|
||||
static final String JOIN_PROFILE = "join-profile";
|
||||
static final String OLD_SUBSELECT_PROFILE = "old-subselect-profile";
|
||||
|
||||
@Test void test(SessionFactoryScope scope) {
|
||||
scope.inTransaction( s-> {
|
||||
|
@ -43,22 +55,109 @@ public class NewFetchTest {
|
|||
});
|
||||
|
||||
F f = scope.fromSession( s -> s.find(F.class, 1));
|
||||
assertFalse( Hibernate.isInitialized( f.g ) );
|
||||
assertFalse( Hibernate.isInitialized( f.es ) );
|
||||
assertFalse( isInitialized( f.g ) );
|
||||
assertFalse( isInitialized( f.es ) );
|
||||
F ff = scope.fromSession( s -> {
|
||||
s.enableFetchProfile(NEW_PROFILE);
|
||||
return s.find(F.class, 1);
|
||||
} );
|
||||
assertTrue( Hibernate.isInitialized( ff.g ) );
|
||||
assertTrue( Hibernate.isInitialized( ff.es ) );
|
||||
assertTrue( isInitialized( ff.g ) );
|
||||
assertTrue( isInitialized( ff.es ) );
|
||||
|
||||
E e = scope.fromSession( s -> s.find(E.class, 1));
|
||||
assertFalse( Hibernate.isInitialized( e.f ) );
|
||||
assertFalse( isInitialized( e.f ) );
|
||||
E ee = scope.fromSession( s -> {
|
||||
s.enableFetchProfile(OLD_PROFILE);
|
||||
return s.find(E.class, 1);
|
||||
} );
|
||||
assertTrue( Hibernate.isInitialized( ee.f ) );
|
||||
assertTrue( isInitialized( ee.f ) );
|
||||
}
|
||||
|
||||
@Test void testStyles(SessionFactoryScope scope) {
|
||||
long id = scope.fromTransaction( s-> {
|
||||
G g = new G();
|
||||
F f = new F();
|
||||
E e = new E();
|
||||
f.g = g;
|
||||
e.f = f;
|
||||
s.persist(g);
|
||||
s.persist(f);
|
||||
s.persist(e);
|
||||
return f.id;
|
||||
});
|
||||
F f0 = scope.fromSession( s -> s.find(F.class, id));
|
||||
assertFalse(isInitialized(f0.es));
|
||||
F f1 = scope.fromSession( s -> {
|
||||
s.enableFetchProfile(SELECT_PROFILE);
|
||||
return s.find(F.class, id);
|
||||
});
|
||||
assertTrue(isInitialized(f1.es));
|
||||
F f2 = scope.fromSession( s -> {
|
||||
s.enableFetchProfile(JOIN_PROFILE);
|
||||
return s.find(F.class, id);
|
||||
});
|
||||
assertTrue(isInitialized(f2.es));
|
||||
F f3 = scope.fromSession( s -> {
|
||||
s.enableFetchProfile(SUBSELECT_PROFILE);
|
||||
return s.find(F.class, id);
|
||||
});
|
||||
assertTrue(isInitialized(f3.es));
|
||||
}
|
||||
|
||||
@Test void subselectTest(SessionFactoryScope scope) {
|
||||
scope.inTransaction(s -> {
|
||||
G g = new G();
|
||||
F f1 = new F();
|
||||
F f2 = new F();
|
||||
E e1 = new E();
|
||||
E e2 = new E();
|
||||
E e3 = new E();
|
||||
E e4 = new E();
|
||||
f1.g = g;
|
||||
f2.g = g;
|
||||
e1.f = f1;
|
||||
e2.f = f1;
|
||||
e3.f = f1;
|
||||
e4.f = f2;
|
||||
s.persist(g);
|
||||
s.persist(f1);
|
||||
s.persist(f2);
|
||||
s.persist(e1);
|
||||
s.persist(e2);
|
||||
s.persist(e3);
|
||||
});
|
||||
scope.inSession( s -> {
|
||||
List<F> fs = s.createSelectionQuery("from F", F.class).getResultList();
|
||||
F f0 = fs.get(0);
|
||||
F f1 = fs.get(1);
|
||||
assertFalse( isInitialized( f0.es ) );
|
||||
assertFalse( isInitialized( f1.es ) );
|
||||
f0.es.size();
|
||||
assertTrue( isInitialized( f0.es ) );
|
||||
assertFalse( isInitialized( f1.es ) );
|
||||
});
|
||||
scope.inSession( s -> {
|
||||
s.enableFetchProfile(SUBSELECT_PROFILE);
|
||||
List<F> fs = s.createSelectionQuery("from F", F.class).getResultList();
|
||||
F f0 = fs.get(0);
|
||||
F f1 = fs.get(1);
|
||||
assertFalse( isInitialized( f0.es ) );
|
||||
assertFalse( isInitialized( f1.es ) );
|
||||
f0.es.size();
|
||||
assertTrue( isInitialized( f0.es ) );
|
||||
assertTrue( isInitialized( f1.es ) );
|
||||
});
|
||||
scope.inSession( s -> {
|
||||
s.enableFetchProfile(OLD_SUBSELECT_PROFILE);
|
||||
List<F> fs = s.createSelectionQuery("from F", F.class).getResultList();
|
||||
F f0 = fs.get(0);
|
||||
F f1 = fs.get(1);
|
||||
assertFalse( isInitialized( f0.es ) );
|
||||
assertFalse( isInitialized( f1.es ) );
|
||||
f0.es.size();
|
||||
assertTrue( isInitialized( f0.es ) );
|
||||
assertTrue( isInitialized( f1.es ) );
|
||||
});
|
||||
}
|
||||
|
||||
@Entity(name = "E")
|
||||
|
@ -77,6 +176,9 @@ public class NewFetchTest {
|
|||
G g;
|
||||
@OneToMany(mappedBy = "f")
|
||||
@Fetch(value = JOIN, profile = NEW_PROFILE)
|
||||
@Fetch(value = SUBSELECT, profile = SUBSELECT_PROFILE)
|
||||
@Fetch(value = SELECT, profile = SELECT_PROFILE)
|
||||
@Fetch(value = JOIN, profile = JOIN_PROFILE)
|
||||
Set<E> es;
|
||||
}
|
||||
@Entity(name = "G")
|
||||
|
|
Loading…
Reference in New Issue