/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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 java.util.Set; 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); } /** * Return the attribute names on the current FetchGroup. This does not * include the attributes on nested FetchGroups * * @deprecated Use {@link AttributeGroup#getAttributeNames()} */ @Deprecated public Set<String> getAttributes() { return getAttributeNames(); } /** * INTERNAL: * Called on attempt to get value of an attribute that hasn't been fetched yet. * Returns an error message in case javax.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 javax.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 {@link #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 {@link #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(); } 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)); } }