4040import org .hibernate .sql .results .spi .ManagedResultConsumer ;
4141
4242import org .checkerframework .checker .nullness .qual .NonNull ;
43+ import org .hibernate .type .descriptor .java .JavaType ;
4344
4445import static java .lang .Boolean .TRUE ;
4546import static org .hibernate .internal .util .collections .CollectionHelper .isEmpty ;
47+ import static org .hibernate .loader .ast .internal .CacheEntityLoaderHelper .loadFromSessionCacheStatic ;
4648
4749/**
4850 * @author Steve Ebersole
4951 */
5052public class MultiIdEntityLoaderArrayParam <E > extends AbstractMultiIdEntityLoader <E > implements SqlArrayMultiKeyLoader {
5153 private final JdbcMapping arrayJdbcMapping ;
5254 private final JdbcParameter jdbcParameter ;
55+ private final int idJdbcTypeCount ;
5356
54- public MultiIdEntityLoaderArrayParam (EntityMappingType entityDescriptor , SessionFactoryImplementor sessionFactory ) {
57+ public MultiIdEntityLoaderArrayParam (EntityMappingType entityDescriptor , int identifierColumnSpan , SessionFactoryImplementor sessionFactory ) {
5558 super ( entityDescriptor , sessionFactory );
59+ this .idJdbcTypeCount = identifierColumnSpan ;
5660 final Class <?> arrayClass = createTypedArray ( 0 ).getClass ();
5761 arrayJdbcMapping = MultiKeyLoadHelper .resolveArrayJdbcMapping (
5862 getSessionFactory ().getTypeConfiguration ().getBasicTypeRegistry ().getRegisteredType ( arrayClass ),
@@ -77,88 +81,152 @@ protected <K> List<E> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp
7781 );
7882 }
7983
84+ assert loadOptions .isOrderReturnEnabled ();
85+
8086 final boolean coerce = !getSessionFactory ().getJpaMetamodel ().getJpaCompliance ().isLoadByIdComplianceEnabled ();
81- final LockOptions lockOptions = (loadOptions .getLockOptions () == null )
87+ final JavaType <?> idType = getLoadable ().getIdentifierMapping ().getJavaType ();
88+
89+ final LockOptions lockOptions = loadOptions .getLockOptions () == null
8290 ? new LockOptions ( LockMode .NONE )
8391 : loadOptions .getLockOptions ();
8492
93+ final int maxBatchSize = maxBatchSize ( ids , loadOptions );
94+
8595 final List <Object > result = CollectionHelper .arrayList ( ids .length );
86- List <Object > idsToLoadFromDatabase = null ;
87- List <Integer > idsToLoadFromDatabaseResultIndexes = null ;
8896
89- for ( int i = 0 ; i < ids .length ; i ++ ) {
90- final Object id ;
91- if ( coerce ) {
92- id = getLoadable ().getIdentifierMapping ().getJavaType ().coerce ( ids [ i ], session );
93- }
94- else {
95- id = ids [ i ];
96- }
97+ final List <Object > idsInBatch = new ArrayList <>();
98+ final List <Integer > elementPositionsLoadedByBatch = new ArrayList <>();
9799
100+ for ( int i = 0 ; i < ids .length ; i ++ ) {
101+ final Object id = coerce ? idType .coerce ( ids [i ], session ) : ids [i ];
98102 final EntityKey entityKey = new EntityKey ( id , getLoadable ().getEntityPersister () );
99103
100- if ( loadOptions .isSessionCheckingEnabled () || loadOptions .isSecondLevelCacheCheckingEnabled () ) {
101- LoadEvent loadEvent = new LoadEvent (
102- id ,
103- getLoadable ().getJavaType ().getJavaTypeClass ().getName (),
104- lockOptions ,
105- session ,
106- LoaderHelper .getReadOnlyFromLoadQueryInfluencers (session )
107- );
104+ if ( !loadFromCaches ( loadOptions , session , id , lockOptions , entityKey , result , i ) ) {
105+ // if we did not hit any of the continues above,
106+ // then we need to batch load the entity state.
107+ idsInBatch .add ( id );
108108
109- Object managedEntity = null ;
110-
111- if ( loadOptions .isSessionCheckingEnabled () ) {
112- // look for it in the Session first
113- final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper .loadFromSessionCacheStatic (
114- loadEvent ,
115- entityKey ,
116- LoadEventListener .GET
117- );
118- managedEntity = persistenceContextEntry .getEntity ();
119-
120- if ( managedEntity != null
121- && !loadOptions .isReturnOfDeletedEntitiesEnabled ()
122- && !persistenceContextEntry .isManaged () ) {
123- // put a null in the result
124- result .add ( i , null );
125- continue ;
126- }
109+ if ( idsInBatch .size () >= maxBatchSize ) {
110+ // we've hit the allotted max-batch-size, perform an "intermediate load"
111+ loadEntitiesById ( loadOptions , session , lockOptions , idsInBatch );
112+ idsInBatch .clear ();
127113 }
128114
129- if ( managedEntity == null && loadOptions .isSecondLevelCacheCheckingEnabled () ) {
130- // look for it in the SessionFactory
131- managedEntity = CacheEntityLoaderHelper .INSTANCE .loadFromSecondLevelCache (
132- loadEvent ,
133- getLoadable ().getEntityPersister (),
134- entityKey
135- );
136- }
115+ // Save the EntityKey instance for use later
116+ result .add ( i , entityKey );
117+ elementPositionsLoadedByBatch .add ( i );
118+ }
119+ }
137120
138- if ( managedEntity != null ) {
139- result .add ( i , managedEntity );
140- continue ;
121+ if ( !idsInBatch .isEmpty () ) {
122+ // we still have ids to load from the processing above since
123+ // the last max-batch-size trigger, perform a load for them
124+ loadEntitiesById ( loadOptions , session , lockOptions , idsInBatch );
125+ }
126+
127+ // for each result where we set the EntityKey earlier, replace them
128+ handleResults ( loadOptions , session , elementPositionsLoadedByBatch , result );
129+
130+ //noinspection unchecked
131+ return (List <E >) result ;
132+ }
133+
134+ private boolean loadFromCaches (
135+ MultiIdLoadOptions loadOptions ,
136+ EventSource session ,
137+ Object id ,
138+ LockOptions lockOptions ,
139+ EntityKey entityKey ,
140+ List <Object > result ,
141+ int i ) {
142+ if ( loadOptions .isSessionCheckingEnabled () || loadOptions .isSecondLevelCacheCheckingEnabled () ) {
143+ final LoadEvent loadEvent = new LoadEvent (
144+ id ,
145+ getLoadable ().getJavaType ().getJavaTypeClass ().getName (),
146+ lockOptions ,
147+ session ,
148+ LoaderHelper .getReadOnlyFromLoadQueryInfluencers ( session )
149+ );
150+
151+ Object managedEntity = null ;
152+
153+ if ( loadOptions .isSessionCheckingEnabled () ) {
154+ // look for it in the Session first
155+ final PersistenceContextEntry persistenceContextEntry =
156+ loadFromSessionCacheStatic ( loadEvent , entityKey , LoadEventListener .GET );
157+ managedEntity = persistenceContextEntry .getEntity ();
158+
159+ if ( managedEntity != null
160+ && !loadOptions .isReturnOfDeletedEntitiesEnabled ()
161+ && !persistenceContextEntry .isManaged () ) {
162+ // put a null in the result
163+ result .add ( i , null );
164+ return true ;
141165 }
142166 }
143167
144- // if we did not hit any of the continues above, then we need to batch
145- // load the entity state.
146- if ( idsToLoadFromDatabase == null ) {
147- idsToLoadFromDatabase = new ArrayList <>();
148- idsToLoadFromDatabaseResultIndexes = new ArrayList <>();
168+ if ( managedEntity == null && loadOptions .isSecondLevelCacheCheckingEnabled () ) {
169+ // look for it in the SessionFactory
170+ managedEntity = CacheEntityLoaderHelper .INSTANCE .loadFromSecondLevelCache (
171+ loadEvent ,
172+ getLoadable ().getEntityPersister (),
173+ entityKey
174+ );
175+ }
176+
177+ if ( managedEntity != null ) {
178+ result .add ( i , managedEntity );
179+ return true ;
149180 }
150- // hold its place in the result with the EntityKey, we'll come back to it later
151- result .add ( i , entityKey );
152- idsToLoadFromDatabase .add ( id );
153- idsToLoadFromDatabaseResultIndexes .add ( i );
154181 }
182+ return false ;
183+ }
155184
156- if ( idsToLoadFromDatabase == null ) {
157- // all the given ids were already associated with the Session
158- //noinspection unchecked
159- return (List <E >) result ;
185+ private static void handleResults (
186+ MultiIdLoadOptions loadOptions ,
187+ EventSource session ,
188+ List <Integer > elementPositionsLoadedByBatch ,
189+ List <Object > result ) {
190+ final PersistenceContext persistenceContext = session .getPersistenceContext ();
191+ for ( Integer position : elementPositionsLoadedByBatch ) {
192+ // the element value at this position in the result List should be
193+ // the EntityKey for that entity - reuse it
194+ final EntityKey entityKey = (EntityKey ) result .get ( position );
195+ BatchFetchQueueHelper .removeBatchLoadableEntityKey ( entityKey , session );
196+ Object entity = persistenceContext .getEntity ( entityKey );
197+ if ( entity != null && !loadOptions .isReturnOfDeletedEntitiesEnabled () ) {
198+ // make sure it is not DELETED
199+ final EntityEntry entry = persistenceContext .getEntry ( entity );
200+ if ( entry .getStatus ().isDeletedOrGone () ) {
201+ // the entity is locally deleted, and the options ask that we not return such entities...
202+ entity = null ;
203+ }
204+ else {
205+ entity = persistenceContext .proxyFor ( entity );
206+ }
207+ }
208+ result .set ( position , entity );
209+ }
210+ }
211+
212+ private <K > int maxBatchSize (K [] ids , MultiIdLoadOptions loadOptions ) {
213+ if ( loadOptions .getBatchSize () != null && loadOptions .getBatchSize () > 0 ) {
214+ return loadOptions .getBatchSize ();
215+ }
216+ else {
217+ // disable batching by default
218+ return ids .length ;
219+ // return getSessionFactory().getJdbcServices().getJdbcEnvironment().getDialect()
220+ // .getBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
221+ // idJdbcTypeCount,
222+ // ids.length,
223+ // getSessionFactory().getSessionFactoryOptions().inClauseParameterPaddingEnabled()
224+ // );
160225 }
226+ }
161227
228+ private void loadEntitiesById (
229+ MultiIdLoadOptions loadOptions , EventSource session , LockOptions lockOptions , List <Object > idsToLoadFromDatabase ) {
162230 final SelectStatement sqlAst = LoaderSelectBuilder .createSelectBySingleArrayParameter (
163231 getLoadable (),
164232 getIdentifierMapping (),
@@ -194,37 +262,12 @@ protected <K> List<E> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp
194262 jdbcParameterBindings ,
195263 new ExecutionContextWithSubselectFetchHandler ( session ,
196264 subSelectFetchableKeysHandler ,
197- TRUE .equals ( loadOptions .getReadOnly (session ) ) ),
265+ TRUE .equals ( loadOptions .getReadOnly ( session ) ) ),
198266 RowTransformerStandardImpl .instance (),
199267 null ,
200268 idsToLoadFromDatabase .size (),
201269 ManagedResultConsumer .INSTANCE
202270 );
203-
204- for ( int i = 0 ; i < idsToLoadFromDatabaseResultIndexes .size (); i ++ ) {
205- final Integer resultIndex = idsToLoadFromDatabaseResultIndexes .get (i );
206-
207- // the element value at this position in the result List should be
208- // the EntityKey for that entity - reuse it
209- final EntityKey entityKey = (EntityKey ) result .get ( resultIndex );
210- BatchFetchQueueHelper .removeBatchLoadableEntityKey ( entityKey , session );
211- Object entity = persistenceContext .getEntity ( entityKey );
212- if ( entity != null && !loadOptions .isReturnOfDeletedEntitiesEnabled () ) {
213- // make sure it is not DELETED
214- final EntityEntry entry = persistenceContext .getEntry ( entity );
215- if ( entry .getStatus ().isDeletedOrGone () ) {
216- // the entity is locally deleted, and the options ask that we not return such entities...
217- entity = null ;
218- }
219- else {
220- entity = persistenceContext .proxyFor ( entity );
221- }
222- }
223- result .set ( resultIndex , entity );
224- }
225-
226- //noinspection unchecked
227- return (List <E >) result ;
228271 }
229272
230273
@@ -345,7 +388,7 @@ protected final <R,K> K[] processResolvableEntities(
345388 Object resolvedEntity = null ;
346389
347390 // look for it in the Session first
348- final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper . loadFromSessionCacheStatic (
391+ final PersistenceContextEntry persistenceContextEntry = loadFromSessionCacheStatic (
349392 loadEvent ,
350393 entityKey ,
351394 LoadEventListener .GET
0 commit comments