/
FetchGroup.java
350 lines (321 loc) · 13.7 KB
/
FetchGroup.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/*
* Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// ailitchev - Bug 244124 in 2.1 - Add AttributeGroup for nesting and LoadGroup support
package org.eclipse.persistence.queries;
import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.eclipse.persistence.descriptors.FetchGroupManager;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.queries.AttributeItem;
import org.eclipse.persistence.internal.queries.EntityFetchGroup;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.UnitOfWork;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* A FetchGroup is a performance enhancement that allows a group of attributes
* of an object to be loaded on demand, which means that the data for an
* attribute might not loaded from the underlying data source until an explicit
* access call for the attribute first occurs. It avoids loading all data of the
* object's attributes, in which the user is interested in only a subset of
* them. A great deal of caution and careful system use case analysis should be
* use when using the fetch group feature, as the extra round-trip would well
* offset the gain from the deferred loading in many cases.
* <p>
* FetchGroup usage is only possible when an entity class implements the
* {@link FetchGroupTracker} interface so that the FetchGroup can be stored in
* the entity. The entity must also use the provided check methods to ensure the
* attributes are loaded prior to use. In general this support is enabled
* through weaving of the entity classes. If an entity class does not implement
* {@link FetchGroupTracker} no FetchGroup functionality will be supported and
* attempted use of a FetchGroup in a query will not result in the expected
* behavior.
* <p>
* FetchGroups are defined in 3 ways:
* <ul>
* <li>A {@link FetchGroupManager#getDefaultFetchGroup()} is created and stored
* on the {@link FetchGroupManager} during metadata processing if any of the
* basic ({@link org.eclipse.persistence.mappings.DirectToFieldMapping
* DirectToFieldMapping}) are configured to be loaded directly.
* <li>A named FetchGroup can be defined and added to the
* {@link FetchGroupManager}. For JPA users this can be accomplished using
* annotation (@FetchGroup) or in an eclipselink-orm.xml. For JPA and native
* users named groups can be defined in code and added to the
* {@link FetchGroupManager#addFetchGroup(FetchGroup)}. Adding named groups in
* code is typically done in a {@link
* org.eclipse.persistence.config.DescriptorCustomizer}and should be done
* before the session is initialized at login. To use a named FetchGroup on a
* query the native {@link ObjectLevelReadQuery#setFetchGroupName(String)} can
* be used of for JPA users the {@link
* org.eclipse.persistence.config.QueryHints#FETCH_GROUP_NAME} an be used.
* <li>A dynamic FetchGroup can be created within the application and used on a
* query. For native API usage this is done using
* {@link ObjectLevelReadQuery#setFetchGroup(FetchGroup)} while JPA users
* generally use the {@link org.eclipse.persistence.config.QueryHints#FETCH_GROUP}.
* </ul>
* <p>
* When a query is executed only one FetchGroup will be used. The order of
* precedence is:
* <ol>
* <li>If a FetchGroup is specified on a query it will be used.
* <li>If no FetchGroup is specified but a FetchGroup name is specified and the
* FetchGroupManager has a group by this name it will be used.
* <li>If neither a FetchGroup nor a FetchGroup name is specified on the query
* an the FetchGroupManager has a default group then it will be used.
* <li>If none of these conditions are met then no FetchGroup will be used when
* executing a query.
* </ol><br>
* <i>Note: This includes the execution of queries to populate lazy and eager
* relationships.</i>
* <p>
* <b>Loading:</b> A FetchGroup can optionally specify that it needs its
* included relationships loaded. This can be done using
* {@link #setShouldLoad(boolean)} and {@link #setShouldLoadAll(boolean)} as
* well as the corresponding configurations in the @FetchGroup annotation and
* the {@literal <fetch-group>} element in the eclipselink-orm.xml. When this
* is configured the FetchGroup will also function as a {@link LoadGroup}
* causing all of its specified relationships to be populated prior to returning
* the results from the query execution.
*
* @see FetchGroupManager
* @see org.eclipse.persistence.config.QueryHints#FETCH_GROUP QueryHints.FETCH_GROUP
* @see LoadGroup
*
* @author King Wang, dclarke, ailitchev
* @since TopLink 10.1.3
*/
public class FetchGroup extends AttributeGroup {
/**
* Indicates whether this group should be also used as a {@link LoadGroup}
* when processing the query result.
*/
private boolean shouldLoad;
/**
* Caches the EntityFetch group for this FetchGroup
*/
protected EntityFetchGroup entityFetchGroup;
/**
* Stores a reference to the root entity for an Aggregate Object relationship.
* This ensures that partially loaded aggregates can be triggered.
*/
protected FetchGroupTracker rootEntity;
public FetchGroup() {
super();
}
public FetchGroup(String name) {
super(name);
}
/**
* INTERNAL:
* Called on attempt to get value of an attribute that hasn't been fetched yet.
* Returns an error message in case jakarta.persistence.EntityNotFoundException
* should be thrown by the calling method,
* null otherwise.
* <p>
* This method is typically only invoked through woven code in the
* persistence object introduced when {@link FetchGroupTracker} is woven
* into the entity.
*/
public String onUnfetchedAttribute(FetchGroupTracker entity, String attributeName) {
if (rootEntity != null){
return rootEntity._persistence_getFetchGroup().onUnfetchedAttribute(rootEntity, attributeName);
}
ReadObjectQuery query = new ReadObjectQuery(entity);
query.setShouldUseDefaultFetchGroup(false);
Session session = entity._persistence_getSession();
boolean shouldLoadResultIntoSelectionObject = false;
if (session.isUnitOfWork()) {
shouldLoadResultIntoSelectionObject = !((UnitOfWork)session).isObjectRegistered(entity);
} else {
shouldLoadResultIntoSelectionObject = !session.getIdentityMapAccessor().containsObjectInIdentityMap(entity);
}
if (shouldLoadResultIntoSelectionObject) {
// entity is not in the cache.
// instead of updating object in the cache update entity directly.
query.setShouldLoadResultIntoSelectionObject(true);
// and ignore cache
query.dontCheckCache();
query.setShouldMaintainCache(false);
// To avoid infinite loop clear the fetch group right away.
entity._persistence_setFetchGroup(null);
entity._persistence_setSession(null);
}
Object result = session.executeQuery(query);
if (result == null) {
// the object was not found in the db end exception will be thrown - restore the fetch group back.
if (shouldLoadResultIntoSelectionObject) {
entity._persistence_setFetchGroup(this);
entity._persistence_setSession(session);
}
Object[] args = { query.getSelectionId() };
return ExceptionLocalization.buildMessage("no_entities_retrieved_for_get_reference", args);
}
return null;
}
/**
* INTERNAL:
* Called on attempt to assign value to an attribute that hasn't been fetched yet.
* Returns an error message in case jakarta.persistence.EntityNotFoundException
* should be thrown by the calling method,
* null otherwise.
* <p>
* This method is typically only invoked through woven code in the
* persistence object introduced when {@link FetchGroupTracker} is woven
* into the entity.
*/
public String onUnfetchedAttributeForSet(FetchGroupTracker entity, String attributeName) {
return onUnfetchedAttribute(entity, attributeName);
}
/**
* INTERNAL:
* @return the rootEntity
*/
public FetchGroupTracker getRootEntity() {
return rootEntity;
}
/**
* INTERNAL:
* @param rootEntity the rootEntity to set
*/
public void setRootEntity(FetchGroupTracker rootEntity) {
this.rootEntity = rootEntity;
}
/**
* Configure this group to also act as a {@link LoadGroup} when set to true
* and load all of the specified relationships so that the entities returned
* from the query where this group was used have the requested relationships
* populated. All subsequent attributes added to this group that create a
* nested group will have this value applied to them.
*
* @see #setShouldLoadAll(boolean) to configure #shouldLoad() on
* nested groups
*/
public void setShouldLoad(boolean shouldLoad) {
this.shouldLoad = shouldLoad;
if (this.superClassGroup != null){
((FetchGroup)this.superClassGroup).setShouldLoad(shouldLoad);
}else{
setSubclassShouldLoad(shouldLoad);
}
}
/**
* passes should load to subclasses.
*
* @see #setShouldLoadAll(boolean) to configure #shouldLoad() on
* nested groups
*/
protected void setSubclassShouldLoad(boolean shouldLoad) {
if (this.subClasses != null){
for (CoreAttributeGroup group : this.subClasses){
((FetchGroup)group).shouldLoad = shouldLoad;
((FetchGroup)group).setSubclassShouldLoad(shouldLoad);
}
}
}
/**
* Configure this group to also act as a {@link LoadGroup} the same as
* {@link #setShouldLoad(boolean)}. Additionally this method will apply the
* provided boolean value to all nested groups already added.
*
* @see #setShouldLoad(boolean) to only configure this grup without
* effecting existing nested groups.
*/
public void setShouldLoadAll(boolean shouldLoad) {
this.setShouldLoad(shouldLoad);
if(this.hasItems()) {
Iterator<Map.Entry<String, AttributeItem>> it = getItems().entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, AttributeItem> entry = it.next();
FetchGroup group = (FetchGroup)entry.getValue().getGroup();
if(group != null) {
group.setShouldLoadAll(shouldLoad);
}
}
}
}
/**
* @return true if this group will be used as a {@link LoadGroup}when
* processing the results of a query to force the specified
* relationships to be loaded.
*/
public boolean shouldLoad() {
return this.shouldLoad;
}
@Override
protected FetchGroup newGroup(String name, CoreAttributeGroup parent) {
FetchGroup fetchGroup = new FetchGroup(name);
if(parent != null) {
fetchGroup.setShouldLoad(((FetchGroup)parent).shouldLoad());
}
return fetchGroup;
}
@Override
public boolean isFetchGroup() {
return true;
}
public boolean isEntityFetchGroup() {
return false;
}
/*
* LoadGroup created with all member groups with shouldLoad set to false dropped.
*/
public LoadGroup toLoadGroupLoadOnly() {
return this.toLoadGroup(new HashMap<>(), true);
}
@Override
public FetchGroup clone() {
return (FetchGroup)super.clone();
}
@Override
public LoadGroup toLoadGroup(Map<AttributeGroup, LoadGroup> cloneMap, boolean loadOnly){
if (loadOnly && !this.shouldLoad){
return null;
}
return super.toLoadGroup(cloneMap, loadOnly);
}
/**
* INTERNAL:
* Used to retrieve the EntityFetchGroup for this FetchGroup
* @return the entityFetchGroup
*/
public EntityFetchGroup getEntityFetchGroup(FetchGroupManager fetchGroupManager) {
if (this.entityFetchGroup == null){
this.entityFetchGroup = fetchGroupManager.getEntityFetchGroup(this.getAttributeNames());
}
return entityFetchGroup;
}
/**
* Returns FetchGroup corresponding to the passed (possibly nested) attribute.
*/
@Override
public FetchGroup getGroup(String attributeNameOrPath) {
return (FetchGroup)super.getGroup(attributeNameOrPath);
}
@Override
public void addAttribute(String attributeNameOrPath, CoreAttributeGroup group) {
this.entityFetchGroup = null;
super.addAttribute(attributeNameOrPath, (group != null ? ((AttributeGroup)group).toFetchGroup() : null));
}
@Override
public void addAttribute(String attributeNameOrPath, Collection<? extends CoreAttributeGroup> groups) {
this.entityFetchGroup = null;
super.addAttribute(attributeNameOrPath, groups);
}
@Override
public void addAttributeKey(String attributeNameOrPath, CoreAttributeGroup group) {
this.entityFetchGroup = null;
super.addAttributeKey(attributeNameOrPath, (group != null ? ((AttributeGroup)group).toFetchGroup() : null));
}
}