| /* |
| * 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)); |
| } |
| |
| |
| } |