blob: a73402595df1bb1cd143e3ba61149162cf5a2d0c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
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;
/**
* 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<AttributeGroup, LoadGroup>(), 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));
}
}