Skip to content

Commit 3878227

Browse files
committed
HHH-19939 fix caching of MERGE and REFRESH load plans
1 parent d14d114 commit 3878227

File tree

1 file changed

+45
-25
lines changed

1 file changed

+45
-25
lines changed

hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderStandardImpl.java

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import org.hibernate.LockMode;
1212
import org.hibernate.LockOptions;
1313
import org.hibernate.engine.spi.LoadQueryInfluencers;
14-
import org.hibernate.engine.spi.SessionFactoryImplementor;
1514
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1615
import org.hibernate.loader.ast.spi.CascadingFetchProfile;
1716
import org.hibernate.metamodel.mapping.EntityMappingType;
@@ -24,17 +23,18 @@
2423
*/
2524
public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSupport<T> {
2625

27-
private final EnumMap<LockMode, SingleIdLoadPlan<T>> selectByLockMode = new EnumMap<>( LockMode.class );
28-
private EnumMap<CascadingFetchProfile, SingleIdLoadPlan<T>> selectByInternalCascadeProfile;
26+
private final EnumMap<LockMode, SingleIdLoadPlan<T>> selectByLockMode =
27+
new EnumMap<>( LockMode.class );
28+
private final EnumMap<CascadingFetchProfile, EnumMap<LockMode,SingleIdLoadPlan<T>>> selectByInternalCascadeProfile =
29+
new EnumMap<>( CascadingFetchProfile.class );
2930

3031
private final BiFunction<LockOptions, LoadQueryInfluencers, SingleIdLoadPlan<T>> loadPlanCreator;
3132

3233
public SingleIdEntityLoaderStandardImpl(
3334
EntityMappingType entityDescriptor,
3435
LoadQueryInfluencers loadQueryInfluencers) {
3536
this( entityDescriptor, loadQueryInfluencers,
36-
(lockOptions, influencers) ->
37-
createLoadPlan( entityDescriptor, lockOptions, influencers, influencers.getSessionFactory() ) );
37+
(lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers) );
3838
}
3939

4040
/**
@@ -50,13 +50,11 @@ protected SingleIdEntityLoaderStandardImpl(
5050
// todo (6.0) : consider creating a base AST and "cloning" it
5151
super( entityDescriptor, influencers.getSessionFactory() );
5252
this.loadPlanCreator = loadPlanCreator;
53-
// see org.hibernate.persister.entity.AbstractEntityPersister#createLoaders
54-
// we should preload a few - maybe LockMode.NONE and LockMode.READ
55-
final var noLocking = new LockOptions();
56-
final var singleIdLoadPlan = loadPlanCreator.apply( noLocking, influencers );
57-
if ( isLoadPlanReusable( noLocking, influencers ) ) {
58-
selectByLockMode.put( LockMode.NONE, singleIdLoadPlan );
59-
}
53+
// Preload some load plans (for now only do it for LockMode.NONE)
54+
final var singleIdLoadPlan = loadPlanCreator.apply( LockOptions.NONE, influencers );
55+
// if ( isLoadPlanReusable( LockOptions.NONE, influencers ) ) {
56+
selectByLockMode.put( LockMode.NONE, singleIdLoadPlan );
57+
// }
6058
}
6159

6260
@Override
@@ -114,38 +112,60 @@ private SingleIdLoadPlan<T> getRegularLoadPlan(LockOptions lockOptions, LoadQuer
114112
}
115113

116114
private SingleIdLoadPlan<T> getInternalCascadeLoadPlan(LockOptions lockOptions, LoadQueryInfluencers influencers) {
117-
final var fetchProfile = influencers.getEnabledCascadingFetchProfile();
118-
if ( selectByInternalCascadeProfile == null ) {
119-
selectByInternalCascadeProfile = new EnumMap<>( CascadingFetchProfile.class );
115+
// TODO: It might be more efficient to just instantiate a LoadPlanKey
116+
// object here than it is to maintain an EnumMap of EnumMaps
117+
final var lockMode = lockOptions.getLockMode();
118+
EnumMap<LockMode,SingleIdLoadPlan<T>> map;
119+
if ( isLoadPlanReusable( lockOptions, influencers ) ) {
120+
final var fetchProfile = influencers.getEnabledCascadingFetchProfile();
121+
final var existingMap = selectByInternalCascadeProfile.get( fetchProfile );
122+
if ( existingMap == null ) {
123+
map = new EnumMap<>( LockMode.class );
124+
selectByInternalCascadeProfile.put( fetchProfile, map );
125+
}
126+
else {
127+
final var existing = existingMap.get( lockMode );
128+
if ( existing != null ) {
129+
return existing;
130+
}
131+
else {
132+
map = existingMap;
133+
}
134+
}
120135
}
121136
else {
122-
final var existing = selectByInternalCascadeProfile.get( fetchProfile );
123-
if ( existing != null ) {
124-
return existing;
125-
}
137+
map = null;
126138
}
139+
127140
final var plan = loadPlanCreator.apply( lockOptions, influencers );
128-
selectByInternalCascadeProfile.put( fetchProfile, plan );
141+
if ( map != null ) {
142+
map.put( lockMode, plan );
143+
}
129144
return plan;
130145
}
131146

147+
/**
148+
* We key the caches only by {@link LockMode} and {@link CascadingFetchProfile}.
149+
* If there is a pessimistic lock with non-default options like timeout, a custom
150+
* fetch profile, or an entity graph, we don't cache and reuse the plan.
151+
*/
132152
private boolean isLoadPlanReusable(LockOptions lockOptions, LoadQueryInfluencers influencers) {
133153
if ( lockOptions.getLockMode().isPessimistic() && lockOptions.hasNonDefaultOptions() ) {
134154
return false;
135155
}
136156
else {
137-
return !getLoadable().isAffectedByEntityGraph( influencers )
138-
&& !getLoadable().isAffectedByEnabledFetchProfiles( influencers );
157+
final var loadable = getLoadable();
158+
return !loadable.isAffectedByEntityGraph( influencers )
159+
&& !loadable.isAffectedByEnabledFetchProfiles( influencers );
139160
}
140161
}
141162

142163
private static <T> SingleIdLoadPlan<T> createLoadPlan(
143164
EntityMappingType loadable,
144165
LockOptions lockOptions,
145-
LoadQueryInfluencers influencers,
146-
SessionFactoryImplementor factory) {
147-
166+
LoadQueryInfluencers influencers) {
148167
final var jdbcParametersBuilder = JdbcParametersList.newBuilder();
168+
final var factory = influencers.getSessionFactory();
149169
return new SingleIdLoadPlan<>(
150170
loadable,
151171
loadable.getIdentifierMapping(),

0 commit comments

Comments
 (0)