44 */
55package org .hibernate .loader .ast .internal ;
66
7+ import org .checkerframework .checker .nullness .qual .NonNull ;
78import org .hibernate .LockMode ;
89import org .hibernate .LockOptions ;
910import org .hibernate .engine .jdbc .spi .JdbcServices ;
2021import org .hibernate .sql .exec .spi .JdbcSelectExecutor ;
2122import org .hibernate .type .descriptor .java .JavaType ;
2223
24+ import java .lang .reflect .Array ;
2325import java .util .ArrayList ;
2426import java .util .List ;
2527
2628import static org .hibernate .internal .util .collections .CollectionHelper .arrayList ;
29+ import static org .hibernate .internal .util .collections .CollectionHelper .isEmpty ;
2730import static org .hibernate .loader .ast .internal .CacheEntityLoaderHelper .loadFromSessionCacheStatic ;
2831import static org .hibernate .loader .ast .internal .LoaderHelper .getReadOnlyFromLoadQueryInfluencers ;
2932import static org .hibernate .loader .ast .internal .MultiKeyLoadLogging .MULTI_KEY_LOAD_LOGGER ;
@@ -37,11 +40,13 @@ public abstract class AbstractMultiIdEntityLoader<T> implements MultiIdEntityLoa
3740 private final EntityMappingType entityDescriptor ;
3841 private final SessionFactoryImplementor sessionFactory ;
3942 private final EntityIdentifierMapping identifierMapping ;
43+ protected final Object [] idArray ;
4044
4145 public AbstractMultiIdEntityLoader (EntityMappingType entityDescriptor , SessionFactoryImplementor sessionFactory ) {
4246 this .entityDescriptor = entityDescriptor ;
4347 this .sessionFactory = sessionFactory ;
4448 identifierMapping = getLoadable ().getIdentifierMapping ();
49+ idArray = (Object []) Array .newInstance ( identifierMapping .getJavaType ().getJavaTypeClass (), 0 );
4550 }
4651
4752 protected EntityMappingType getEntityDescriptor () {
@@ -76,31 +81,43 @@ public EntityMappingType getLoadable() {
7681 @ Override
7782 public final <K > List <T > load (K [] ids , MultiIdLoadOptions loadOptions , EventSource session ) {
7883 assert ids != null ;
79- if ( loadOptions .isOrderReturnEnabled () ) {
80- return performOrderedMultiLoad ( ids , loadOptions , session );
81- }
82- else {
83- return performUnorderedMultiLoad ( ids , loadOptions , session );
84+ return loadOptions .isOrderReturnEnabled ()
85+ ? performOrderedMultiLoad ( ids , loadOptions , session )
86+ : performUnorderedMultiLoad ( ids , loadOptions , session );
87+ }
88+
89+ private List <T > performUnorderedMultiLoad (
90+ Object [] ids ,
91+ MultiIdLoadOptions loadOptions ,
92+ EventSource session ) {
93+ assert !loadOptions .isOrderReturnEnabled ();
94+ assert ids != null ;
95+ if ( MULTI_KEY_LOAD_LOGGER .isTraceEnabled () ) {
96+ MULTI_KEY_LOAD_LOGGER .tracef ( "#performUnorderedMultiLoad(`%s`, ..)" , getLoadable ().getEntityName () );
8497 }
98+ return unorderedMultiLoad ( ids , loadOptions , lockOptions ( loadOptions ), session );
8599 }
86100
87101 protected List <T > performOrderedMultiLoad (
88102 Object [] ids ,
89103 MultiIdLoadOptions loadOptions ,
90104 EventSource session ) {
105+ assert loadOptions .isOrderReturnEnabled ();
106+ assert ids != null ;
91107 if ( MULTI_KEY_LOAD_LOGGER .isTraceEnabled () ) {
92108 MULTI_KEY_LOAD_LOGGER .tracef ( "#performOrderedMultiLoad(`%s`, ..)" , getLoadable ().getEntityName () );
93109 }
110+ return orderedMultiLoad ( ids , loadOptions , lockOptions ( loadOptions ), session );
111+ }
94112
95- assert loadOptions .isOrderReturnEnabled ();
96-
97- final boolean coerce = !getSessionFactory ().getJpaMetamodel ().getJpaCompliance ().isLoadByIdComplianceEnabled ();
113+ private List <T > orderedMultiLoad (
114+ Object [] ids ,
115+ MultiIdLoadOptions loadOptions ,
116+ LockOptions lockOptions ,
117+ EventSource session ) {
118+ final boolean idCoercionEnabled = isIdCoercionEnabled ();
98119 final JavaType <?> idType = getLoadable ().getIdentifierMapping ().getJavaType ();
99120
100- final LockOptions lockOptions = loadOptions .getLockOptions () == null
101- ? new LockOptions ( LockMode .NONE )
102- : loadOptions .getLockOptions ();
103-
104121 final int maxBatchSize = maxBatchSize ( ids , loadOptions );
105122
106123 final List <Object > result = arrayList ( ids .length );
@@ -109,10 +126,10 @@ protected List<T> performOrderedMultiLoad(
109126 final List <Integer > elementPositionsLoadedByBatch = new ArrayList <>();
110127
111128 for ( int i = 0 ; i < ids .length ; i ++ ) {
112- final Object id = coerce ? idType .coerce ( ids [i ], session ) : ids [i ];
129+ final Object id = idCoercionEnabled ? idType .coerce ( ids [i ], session ) : ids [i ];
113130 final EntityKey entityKey = new EntityKey ( id , getLoadable ().getEntityPersister () );
114131
115- if ( !loadFromCaches ( loadOptions , session , id , lockOptions , entityKey , result , i ) ) {
132+ if ( !loadFromEnabledCaches ( loadOptions , session , id , lockOptions , entityKey , result , i ) ) {
116133 // if we did not hit any of the continues above,
117134 // then we need to batch load the entity state.
118135 idsInBatch .add ( id );
@@ -142,13 +159,27 @@ protected List<T> performOrderedMultiLoad(
142159 return (List <T >) result ;
143160 }
144161
162+ protected static LockOptions lockOptions (MultiIdLoadOptions loadOptions ) {
163+ return loadOptions .getLockOptions () == null
164+ ? new LockOptions ( LockMode .NONE )
165+ : loadOptions .getLockOptions ();
166+ }
167+
145168 protected abstract int maxBatchSize (Object [] ids , MultiIdLoadOptions loadOptions );
146169
147- protected abstract void handleResults (MultiIdLoadOptions loadOptions , EventSource session , List <Integer > elementPositionsLoadedByBatch , List <Object > result );
170+ protected abstract void handleResults (
171+ MultiIdLoadOptions loadOptions ,
172+ EventSource session ,
173+ List <Integer > elementPositionsLoadedByBatch ,
174+ List <Object > result );
148175
149- protected abstract void loadEntitiesById (List <Object > idsInBatch , LockOptions lockOptions , MultiIdLoadOptions loadOptions , EventSource session );
176+ protected abstract void loadEntitiesById (
177+ List <Object > idsInBatch ,
178+ LockOptions lockOptions ,
179+ MultiIdLoadOptions loadOptions ,
180+ EventSource session );
150181
151- protected boolean loadFromCaches (
182+ protected boolean loadFromEnabledCaches (
152183 MultiIdLoadOptions loadOptions ,
153184 EventSource session ,
154185 Object id ,
@@ -157,48 +188,189 @@ protected boolean loadFromCaches(
157188 List <Object > result ,
158189 int i ) {
159190 if ( loadOptions .isSessionCheckingEnabled () || loadOptions .isSecondLevelCacheCheckingEnabled () ) {
160- final LoadEvent loadEvent = new LoadEvent (
161- id ,
162- getLoadable ().getJavaType ().getJavaTypeClass ().getName (),
163- lockOptions ,
164- session ,
165- getReadOnlyFromLoadQueryInfluencers ( session )
191+ return loadFromCaches ( loadOptions , entityKey , result , i ,
192+ new LoadEvent (
193+ id ,
194+ getLoadable ().getJavaType ().getJavaTypeClass ().getName (),
195+ lockOptions ,
196+ session ,
197+ getReadOnlyFromLoadQueryInfluencers ( session )
198+ )
166199 );
200+ }
201+ else {
202+ return false ;
203+ }
204+ }
167205
168- Object managedEntity = null ;
206+ private boolean loadFromCaches (
207+ MultiIdLoadOptions loadOptions ,
208+ EntityKey entityKey ,
209+ List <Object > result ,
210+ int i ,
211+ LoadEvent loadEvent ) {
212+ Object managedEntity = null ;
213+
214+ if ( loadOptions .isSessionCheckingEnabled () ) {
215+ // look for it in the Session first
216+ final CacheEntityLoaderHelper .PersistenceContextEntry persistenceContextEntry =
217+ loadFromSessionCacheStatic ( loadEvent , entityKey , LoadEventListener .GET );
218+ managedEntity = persistenceContextEntry .getEntity ();
219+
220+ if ( managedEntity != null
221+ && !loadOptions .isReturnOfDeletedEntitiesEnabled ()
222+ && !persistenceContextEntry .isManaged () ) {
223+ // put a null in the result
224+ result .add ( i , null );
225+ return true ;
226+ }
227+ }
169228
170- if ( loadOptions .isSessionCheckingEnabled () ) {
171- // look for it in the Session first
172- final CacheEntityLoaderHelper .PersistenceContextEntry persistenceContextEntry =
173- loadFromSessionCacheStatic ( loadEvent , entityKey , LoadEventListener .GET );
174- managedEntity = persistenceContextEntry .getEntity ();
229+ if ( managedEntity == null
230+ && loadOptions .isSecondLevelCacheCheckingEnabled () ) {
231+ // look for it in the SessionFactory
232+ managedEntity = CacheEntityLoaderHelper .INSTANCE .loadFromSecondLevelCache (
233+ loadEvent ,
234+ getLoadable ().getEntityPersister (),
235+ entityKey
236+ );
237+ }
175238
176- if ( managedEntity != null
177- && !loadOptions .isReturnOfDeletedEntitiesEnabled ()
178- && !persistenceContextEntry .isManaged () ) {
179- // put a null in the result
180- result .add ( i , null );
181- return true ;
182- }
183- }
239+ if ( managedEntity != null ) {
240+ result .add ( i , managedEntity );
241+ return true ;
242+ }
243+ else {
244+ return false ;
245+ }
246+ }
247+
248+ protected List <T > unorderedMultiLoad (
249+ Object [] ids ,
250+ MultiIdLoadOptions loadOptions ,
251+ LockOptions lockOptions ,
252+ EventSource session ) {
253+ final List <T > result = arrayList ( ids .length );
254+ final Object [] unresolvableIds =
255+ resolveInCachesIfEnabled ( ids , loadOptions , lockOptions , session ,
256+ (position , entityKey , resolvedRef ) -> result .add ( (T ) resolvedRef ) );
257+ if ( !isEmpty ( unresolvableIds ) ) {
258+ loadEntitiesWithUnresolvedIds ( loadOptions , lockOptions , session , unresolvableIds , result );
259+ }
260+ return result ;
261+ }
262+
263+ protected abstract void loadEntitiesWithUnresolvedIds (
264+ MultiIdLoadOptions loadOptions ,
265+ LockOptions lockOptions ,
266+ EventSource session ,
267+ Object [] unresolvableIds ,
268+ List <T > result );
269+
270+ protected final <R > Object [] resolveInCachesIfEnabled (
271+ Object [] ids ,
272+ @ NonNull MultiIdLoadOptions loadOptions ,
273+ @ NonNull LockOptions lockOptions ,
274+ EventSource session ,
275+ ResolutionConsumer <R > resolutionConsumer ) {
276+ return loadOptions .isSessionCheckingEnabled () || loadOptions .isSecondLevelCacheCheckingEnabled ()
277+ // the user requested that we exclude ids corresponding to already managed
278+ // entities from the generated load SQL. So here we will iterate all
279+ // incoming id values and see whether it corresponds to an existing
280+ // entity associated with the PC - if it does we add it to the result
281+ // list immediately and remove its id from the group of ids to load.
282+ // we'll load all of them from the database
283+ ? resolveInCaches ( ids , loadOptions , lockOptions , session , resolutionConsumer )
284+ : ids ;
285+ }
286+
287+ protected final <R > Object [] resolveInCaches (
288+ Object [] ids ,
289+ MultiIdLoadOptions loadOptions ,
290+ LockOptions lockOptions ,
291+ EventSource session ,
292+ ResolutionConsumer <R > resolutionConsumer ) {
293+
294+ final boolean idCoercionEnabled = isIdCoercionEnabled ();
295+ final JavaType <?> idType = getLoadable ().getIdentifierMapping ().getJavaType ();
184296
185- if ( managedEntity == null && loadOptions .isSecondLevelCacheCheckingEnabled () ) {
186- // look for it in the SessionFactory
187- managedEntity = CacheEntityLoaderHelper .INSTANCE .loadFromSecondLevelCache (
188- loadEvent ,
189- getLoadable ().getEntityPersister (),
190- entityKey
191- );
297+ List <Object > unresolvedIds = null ;
298+ for ( int i = 0 ; i < ids .length ; i ++ ) {
299+ final Object id = idCoercionEnabled ? idType .coerce ( ids [i ], session ) : ids [i ];
300+ final EntityKey entityKey = new EntityKey ( id , getLoadable ().getEntityPersister () );
301+ unresolvedIds = loadFromCaches ( id , entityKey , i , unresolvedIds , loadOptions , resolutionConsumer ,
302+ new LoadEvent (
303+ id ,
304+ getLoadable ().getJavaType ().getJavaTypeClass ().getName (),
305+ lockOptions ,
306+ session ,
307+ getReadOnlyFromLoadQueryInfluencers ( session )
308+ )
309+ );
310+ }
311+
312+ if ( isEmpty ( unresolvedIds ) ) {
313+ // all the given ids were already associated with the Session
314+ return null ;
315+ }
316+ else if ( unresolvedIds .size () == ids .length ) {
317+ // we need to load all the ids
318+ return ids ;
319+ }
320+ else {
321+ // we need to load only some the ids
322+ return unresolvedIds .toArray ( idArray );
323+ }
324+ }
325+
326+ private boolean isIdCoercionEnabled () {
327+ return !getSessionFactory ().getJpaMetamodel ().getJpaCompliance ().isLoadByIdComplianceEnabled ();
328+ }
329+
330+ public interface ResolutionConsumer <T > {
331+ void consume (int position , EntityKey entityKey , T resolvedRef );
332+ }
333+ private <R , K > List <K > loadFromCaches (
334+ K id , EntityKey entityKey , int i ,
335+ List <K > unresolvedIds ,
336+ MultiIdLoadOptions loadOptions ,
337+ ResolutionConsumer <R > resolutionConsumer ,
338+ LoadEvent loadEvent ) {
339+ Object cachedEntity = null ;
340+
341+ // look for it in the Session first
342+ final CacheEntityLoaderHelper .PersistenceContextEntry persistenceContextEntry =
343+ loadFromSessionCacheStatic ( loadEvent , entityKey , LoadEventListener .GET );
344+ if ( loadOptions .isSessionCheckingEnabled () ) {
345+ cachedEntity = persistenceContextEntry .getEntity ();
346+
347+ if ( cachedEntity != null
348+ && !loadOptions .isReturnOfDeletedEntitiesEnabled ()
349+ && !persistenceContextEntry .isManaged () ) {
350+ resolutionConsumer .consume ( i , entityKey , null );
351+ return unresolvedIds ;
192352 }
353+ }
193354
194- if ( managedEntity != null ) {
195- result .add ( i , managedEntity );
196- return true ;
355+ if ( cachedEntity == null && loadOptions .isSecondLevelCacheCheckingEnabled () ) {
356+ cachedEntity = CacheEntityLoaderHelper .INSTANCE .loadFromSecondLevelCache (
357+ loadEvent ,
358+ getLoadable ().getEntityPersister (),
359+ entityKey
360+ );
361+ }
362+
363+ if ( cachedEntity != null ) {
364+ //noinspection unchecked
365+ resolutionConsumer .consume ( i , entityKey , (R ) cachedEntity );
366+ }
367+ else {
368+ if ( unresolvedIds == null ) {
369+ unresolvedIds = new ArrayList <>();
197370 }
371+ unresolvedIds .add ( id );
198372 }
199- return false ;
373+ return unresolvedIds ;
200374 }
201375
202- protected abstract <K > List <T > performUnorderedMultiLoad (K [] ids , MultiIdLoadOptions loadOptions , EventSource session );
203-
204376}
0 commit comments