40
40
import org .hibernate .sql .results .spi .ManagedResultConsumer ;
41
41
42
42
import org .checkerframework .checker .nullness .qual .NonNull ;
43
+ import org .hibernate .type .descriptor .java .JavaType ;
43
44
44
45
import static java .lang .Boolean .TRUE ;
45
46
import static org .hibernate .internal .util .collections .CollectionHelper .isEmpty ;
47
+ import static org .hibernate .loader .ast .internal .CacheEntityLoaderHelper .loadFromSessionCacheStatic ;
46
48
47
49
/**
48
50
* @author Steve Ebersole
49
51
*/
50
52
public class MultiIdEntityLoaderArrayParam <E > extends AbstractMultiIdEntityLoader <E > implements SqlArrayMultiKeyLoader {
51
53
private final JdbcMapping arrayJdbcMapping ;
52
54
private final JdbcParameter jdbcParameter ;
55
+ private final int idJdbcTypeCount ;
53
56
54
- public MultiIdEntityLoaderArrayParam (EntityMappingType entityDescriptor , SessionFactoryImplementor sessionFactory ) {
57
+ public MultiIdEntityLoaderArrayParam (EntityMappingType entityDescriptor , int identifierColumnSpan , SessionFactoryImplementor sessionFactory ) {
55
58
super ( entityDescriptor , sessionFactory );
59
+ this .idJdbcTypeCount = identifierColumnSpan ;
56
60
final Class <?> arrayClass = createTypedArray ( 0 ).getClass ();
57
61
arrayJdbcMapping = MultiKeyLoadHelper .resolveArrayJdbcMapping (
58
62
getSessionFactory ().getTypeConfiguration ().getBasicTypeRegistry ().getRegisteredType ( arrayClass ),
@@ -77,88 +81,152 @@ protected <K> List<E> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp
77
81
);
78
82
}
79
83
84
+ assert loadOptions .isOrderReturnEnabled ();
85
+
80
86
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
82
90
? new LockOptions ( LockMode .NONE )
83
91
: loadOptions .getLockOptions ();
84
92
93
+ final int maxBatchSize = maxBatchSize ( ids , loadOptions );
94
+
85
95
final List <Object > result = CollectionHelper .arrayList ( ids .length );
86
- List <Object > idsToLoadFromDatabase = null ;
87
- List <Integer > idsToLoadFromDatabaseResultIndexes = null ;
88
96
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 <>();
97
99
100
+ for ( int i = 0 ; i < ids .length ; i ++ ) {
101
+ final Object id = coerce ? idType .coerce ( ids [i ], session ) : ids [i ];
98
102
final EntityKey entityKey = new EntityKey ( id , getLoadable ().getEntityPersister () );
99
103
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 );
108
108
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 ();
127
113
}
128
114
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
+ }
137
120
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 ;
141
165
}
142
166
}
143
167
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 ;
149
180
}
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 );
154
181
}
182
+ return false ;
183
+ }
155
184
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
+ // );
160
225
}
226
+ }
161
227
228
+ private void loadEntitiesById (
229
+ MultiIdLoadOptions loadOptions , EventSource session , LockOptions lockOptions , List <Object > idsToLoadFromDatabase ) {
162
230
final SelectStatement sqlAst = LoaderSelectBuilder .createSelectBySingleArrayParameter (
163
231
getLoadable (),
164
232
getIdentifierMapping (),
@@ -194,37 +262,12 @@ protected <K> List<E> performOrderedMultiLoad(K[] ids, MultiIdLoadOptions loadOp
194
262
jdbcParameterBindings ,
195
263
new ExecutionContextWithSubselectFetchHandler ( session ,
196
264
subSelectFetchableKeysHandler ,
197
- TRUE .equals ( loadOptions .getReadOnly (session ) ) ),
265
+ TRUE .equals ( loadOptions .getReadOnly ( session ) ) ),
198
266
RowTransformerStandardImpl .instance (),
199
267
null ,
200
268
idsToLoadFromDatabase .size (),
201
269
ManagedResultConsumer .INSTANCE
202
270
);
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 ;
228
271
}
229
272
230
273
@@ -345,7 +388,7 @@ protected final <R,K> K[] processResolvableEntities(
345
388
Object resolvedEntity = null ;
346
389
347
390
// look for it in the Session first
348
- final PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper . loadFromSessionCacheStatic (
391
+ final PersistenceContextEntry persistenceContextEntry = loadFromSessionCacheStatic (
349
392
loadEvent ,
350
393
entityKey ,
351
394
LoadEventListener .GET
0 commit comments