1111import org .hibernate .LockMode ;
1212import org .hibernate .LockOptions ;
1313import org .hibernate .engine .spi .LoadQueryInfluencers ;
14- import org .hibernate .engine .spi .SessionFactoryImplementor ;
1514import org .hibernate .engine .spi .SharedSessionContractImplementor ;
1615import org .hibernate .loader .ast .spi .CascadingFetchProfile ;
1716import org .hibernate .metamodel .mapping .EntityMappingType ;
2423 */
2524public 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