mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-22 11:06:08 +00:00
HHH-8776 fix 'fetch graph' semantic
This commit is contained in:
parent
a60a10f009
commit
f3cfff5cee
@ -206,10 +206,6 @@ include::{sourcedir}/GraphFetchingTest.java[tags=fetching-strategies-dynamic-fet
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Although the JPA standard specifies that you can override an EAGER fetching association at runtime using the `javax.persistence.fetchgraph` hint,
|
||||
currently, Hibernate does not implement this feature, so EAGER associations cannot be fetched lazily.
|
||||
For more info, check out the https://hibernate.atlassian.net/browse/HHH-8776[HHH-8776] Jira issue.
|
||||
|
||||
When executing a JPQL query, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly,
|
||||
which can lead to N+1 query issues.
|
||||
|
||||
@ -648,4 +644,4 @@ include::{extrasdir}/fetching-LazyCollection-select-example.sql[]
|
||||
Therefore, the child entities were fetched one after the other without triggering a full collection initialization.
|
||||
|
||||
For this reason, caution is advised since accessing all elements using `LazyCollectionOption.EXTRA` can lead to N+1 query issues.
|
||||
====
|
||||
====
|
||||
|
@ -32,6 +32,8 @@
|
||||
import org.hibernate.event.spi.PostLoadEventListener;
|
||||
import org.hibernate.event.spi.PreLoadEvent;
|
||||
import org.hibernate.event.spi.PreLoadEventListener;
|
||||
import org.hibernate.graph.spi.AttributeNodeImplementor;
|
||||
import org.hibernate.graph.spi.GraphImplementor;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
@ -184,8 +186,12 @@ private static void doInitializeEntity(
|
||||
String entityName = persister.getEntityName();
|
||||
String[] propertyNames = persister.getPropertyNames();
|
||||
final Type[] types = persister.getPropertyTypes();
|
||||
|
||||
GraphImplementor<?> fetchGraphContext = session.getFetchGraphLoadContext();
|
||||
|
||||
for ( int i = 0; i < hydratedState.length; i++ ) {
|
||||
final Object value = hydratedState[i];
|
||||
|
||||
if ( debugEnabled ) {
|
||||
LOG.debugf(
|
||||
"Processing attribute `%s` : value = %s",
|
||||
@ -207,7 +213,7 @@ private static void doInitializeEntity(
|
||||
// IMPLEMENTATION NOTE: this is a lazy collection property on a bytecode-enhanced entity.
|
||||
// HHH-10989: We need to resolve the collection so that a CollectionReference is added to StatefulPersistentContext.
|
||||
// As mentioned above, hydratedState[i] needs to remain LazyPropertyInitializer.UNFETCHED_PROPERTY
|
||||
// so do not assign the resolved, unitialized PersistentCollection back to hydratedState[i].
|
||||
// so do not assign the resolved, uninitialized PersistentCollection back to hydratedState[i].
|
||||
Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled );
|
||||
types[i].resolve( value, session, entity, overridingEager );
|
||||
}
|
||||
@ -230,6 +236,10 @@ else if ( value != PropertyAccessStrategyBackRefImpl.UNKNOWN ) {
|
||||
LOG.debugf( "Skipping <unknown> attribute : `%s`", propertyNames[i] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( session.getFetchGraphLoadContext() != fetchGraphContext ) {
|
||||
session.setFetchGraphLoadContext( fetchGraphContext );
|
||||
}
|
||||
}
|
||||
|
||||
//Must occur after resolving identifiers!
|
||||
@ -367,27 +377,45 @@ public static void afterInitialize(
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if eager of the association is overriden by anything.
|
||||
* Check if eager of the association is overridden by anything.
|
||||
*
|
||||
* @param session session
|
||||
* @param entityName entity name
|
||||
* @param associationName association name
|
||||
*
|
||||
* @param associationType association type
|
||||
* @param isDebugEnabled if debug log level enabled
|
||||
* @return null if there is no overriding, true if it is overridden to eager and false if it is overridden to lazy
|
||||
*/
|
||||
private static Boolean getOverridingEager(
|
||||
final SharedSessionContractImplementor session,
|
||||
final String entityName,
|
||||
final String associationName,
|
||||
final Type type,
|
||||
final Type associationType,
|
||||
final boolean isDebugEnabled) {
|
||||
// Performance: check type.isCollectionType() first, as type.isAssociationType() is megamorphic
|
||||
if ( type.isCollectionType() || type.isAssociationType() ) {
|
||||
final Boolean overridingEager = isEagerFetchProfile( session, entityName, associationName );
|
||||
if ( associationType.isCollectionType() || associationType.isAssociationType() ) {
|
||||
|
||||
//This method is very hot, and private so let's piggy back on the fact that the caller already knows the debugging state.
|
||||
if ( isDebugEnabled ) {
|
||||
if ( overridingEager != null ) {
|
||||
Boolean overridingEager = isEagerFetchGraph( session, associationName, associationType );
|
||||
|
||||
if ( overridingEager != null ) {
|
||||
//This method is very hot, and private so let's piggy back on the fact that the caller already knows the debugging state.
|
||||
if ( isDebugEnabled ) {
|
||||
LOG.debugf(
|
||||
"Overriding eager fetching using fetch graph. EntityName: %s, associationName: %s, eager fetching: %s",
|
||||
entityName,
|
||||
associationName,
|
||||
overridingEager
|
||||
);
|
||||
}
|
||||
|
||||
return overridingEager;
|
||||
}
|
||||
|
||||
overridingEager = isEagerFetchProfile( session, entityName, associationName );
|
||||
|
||||
if ( overridingEager != null ) {
|
||||
//This method is very hot, and private so let's piggy back on the fact that the caller already knows the debugging state.
|
||||
if ( isDebugEnabled ) {
|
||||
LOG.debugf(
|
||||
"Overriding eager fetching using active fetch profile. EntityName: %s, associationName: %s, eager fetching: %s",
|
||||
entityName,
|
||||
@ -395,9 +423,8 @@ private static Boolean getOverridingEager(
|
||||
overridingEager
|
||||
);
|
||||
}
|
||||
return overridingEager;
|
||||
}
|
||||
|
||||
return overridingEager;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -421,6 +448,37 @@ private static Boolean isEagerFetchProfile(SharedSessionContractImplementor sess
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Boolean isEagerFetchGraph(SharedSessionContractImplementor session, String associationName, Type associationType) {
|
||||
GraphImplementor<?> context = session.getFetchGraphLoadContext();
|
||||
|
||||
if ( context != null ) {
|
||||
// 'fetch graph' is in effect, so null should not be returned
|
||||
AttributeNodeImplementor<Object> attributeNode = context.findAttributeNode( associationName );
|
||||
if ( attributeNode != null ) {
|
||||
if ( associationType.isCollectionType() ) {
|
||||
session.setFetchGraphLoadContext( null );
|
||||
}
|
||||
else {
|
||||
// set 'fetchGraphContext' to sub-graph so graph is explored further (internal loading)
|
||||
GraphImplementor<?> subContext = attributeNode.getSubGraphMap().get( associationType.getReturnedClass() );
|
||||
if ( subContext != null ) {
|
||||
session.setFetchGraphLoadContext( subContext );
|
||||
} else {
|
||||
session.setFetchGraphLoadContext( null );
|
||||
}
|
||||
}
|
||||
// explicit 'fetch graph' applies, so fetch eagerly
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// implicit 'fetch graph' applies, so fetch lazily
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* PostLoad cannot occur during initializeEntity, as that call occurs *before*
|
||||
|
@ -30,6 +30,7 @@
|
||||
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
|
||||
import org.hibernate.graph.spi.GraphImplementor;
|
||||
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||
import org.hibernate.jpa.spi.HibernateEntityManagerImplementor;
|
||||
import org.hibernate.loader.custom.CustomQuery;
|
||||
@ -40,6 +41,7 @@
|
||||
import org.hibernate.resource.jdbc.spi.JdbcSessionOwner;
|
||||
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
|
||||
import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder.Options;
|
||||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
|
||||
/**
|
||||
@ -522,4 +524,31 @@ <T> QueryImplementor<T> createQuery(
|
||||
*/
|
||||
PersistenceContext getPersistenceContextInternal();
|
||||
|
||||
/**
|
||||
* Get the current fetch graph context (either {@link org.hibernate.graph.spi.RootGraphImplementor} or {@link org.hibernate.graph.spi.SubGraphImplementor}.
|
||||
* Suppose fetch graph is "a(b(c))", then during {@link org.hibernate.engine.internal.TwoPhaseLoad}:
|
||||
* <ul>
|
||||
* <li>when loading root</li>: {@link org.hibernate.graph.spi.RootGraphImplementor root} will be returned
|
||||
* <li>when internally loading 'a'</li>: {@link org.hibernate.graph.spi.SubGraphImplementor subgraph} of 'a' will be returned
|
||||
* <li>when internally loading 'b'</li>: {@link org.hibernate.graph.spi.SubGraphImplementor subgraph} of 'a(b)' will be returned
|
||||
* <li>when internally loading 'c'</li>: {@link org.hibernate.graph.spi.SubGraphImplementor subgraph} of 'a(b(c))' will be returned
|
||||
* </ul>
|
||||
*
|
||||
* @return current fetch graph context; can be null if fetch graph is not effective or the graph eager loading is done.
|
||||
* @see #setFetchGraphLoadContext(GraphImplementor)
|
||||
* @see org.hibernate.engine.internal.TwoPhaseLoad
|
||||
*/
|
||||
default GraphImplementor getFetchGraphLoadContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current fetch graph context (either {@link org.hibernate.graph.spi.RootGraphImplementor} or {@link org.hibernate.graph.spi.SubGraphImplementor}.
|
||||
*
|
||||
* @param fetchGraphLoadContext new fetch graph context; can be null (this field will be set to null after root entity loading is done).
|
||||
* @see #getFetchGraphLoadContext()
|
||||
*/
|
||||
default void setFetchGraphLoadContext(GraphImplementor fetchGraphLoadContext) {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,11 +16,8 @@ public enum GraphSemantic {
|
||||
/**
|
||||
* Indicates a "fetch graph" EntityGraph. Attributes explicitly specified
|
||||
* as AttributeNodes are treated as FetchType.EAGER (via join fetch or
|
||||
* subsequent select).
|
||||
* <p/>
|
||||
* Note: Currently, attributes that are not specified are treated as
|
||||
* FetchType.LAZY or FetchType.EAGER depending on the attribute's definition
|
||||
* in metadata, rather than forcing FetchType.LAZY.
|
||||
* subsequent select). Attributes that are not specified are treated as
|
||||
* FetchType.LAZY invariably.
|
||||
*/
|
||||
FETCH( "javax.persistence.fetchgraph" ),
|
||||
|
||||
@ -29,7 +26,7 @@ public enum GraphSemantic {
|
||||
* as AttributeNodes are treated as FetchType.EAGER (via join fetch or
|
||||
* subsequent select). Attributes that are not specified are treated as
|
||||
* FetchType.LAZY or FetchType.EAGER depending on the attribute's definition
|
||||
* in metadata
|
||||
* in metadata.
|
||||
*/
|
||||
LOAD( "javax.persistence.loadgraph" );
|
||||
|
||||
|
@ -130,6 +130,7 @@
|
||||
import org.hibernate.graph.GraphSemantic;
|
||||
import org.hibernate.graph.RootGraph;
|
||||
import org.hibernate.graph.internal.RootGraphImpl;
|
||||
import org.hibernate.graph.spi.GraphImplementor;
|
||||
import org.hibernate.graph.spi.RootGraphImplementor;
|
||||
import org.hibernate.hql.spi.QueryTranslator;
|
||||
import org.hibernate.internal.CriteriaImpl.CriterionEntry;
|
||||
@ -218,6 +219,8 @@ public class SessionImpl
|
||||
private transient LoadEvent loadEvent; //cached LoadEvent instance
|
||||
|
||||
private transient TransactionObserver transactionObserver;
|
||||
|
||||
private transient GraphImplementor fetchGraphLoadContext;
|
||||
|
||||
public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
|
||||
super( factory, options );
|
||||
@ -3332,6 +3335,10 @@ public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode
|
||||
lockOptions = buildLockOptions( lockModeType, properties );
|
||||
loadAccess.with( lockOptions );
|
||||
}
|
||||
|
||||
if ( getLoadQueryInfluencers().getEffectiveEntityGraph().getSemantic() == GraphSemantic.FETCH ) {
|
||||
setFetchGraphLoadContext( getLoadQueryInfluencers().getEffectiveEntityGraph().getGraph() );
|
||||
}
|
||||
|
||||
return loadAccess.load( (Serializable) primaryKey );
|
||||
}
|
||||
@ -3377,6 +3384,7 @@ public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode
|
||||
finally {
|
||||
getLoadQueryInfluencers().getEffectiveEntityGraph().clear();
|
||||
getLoadQueryInfluencers().setReadOnly( null );
|
||||
setFetchGraphLoadContext( null );
|
||||
}
|
||||
}
|
||||
|
||||
@ -3749,6 +3757,16 @@ public List getEntityGraphs(Class entityClass) {
|
||||
checkOpen();
|
||||
return getEntityManagerFactory().findEntityGraphsByType( entityClass );
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphImplementor getFetchGraphLoadContext() {
|
||||
return this.fetchGraphLoadContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFetchGraphLoadContext(GraphImplementor fetchGraphLoadContext) {
|
||||
this.fetchGraphLoadContext = fetchGraphLoadContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by JDK serialization...
|
||||
|
@ -1436,6 +1436,9 @@ protected void beforeQuery() {
|
||||
sessionCacheMode = getProducer().getCacheMode();
|
||||
getProducer().setCacheMode( effectiveCacheMode );
|
||||
}
|
||||
if ( entityGraphQueryHint != null && entityGraphQueryHint.getSemantic() == GraphSemantic.FETCH ) {
|
||||
getProducer().setFetchGraphLoadContext( entityGraphQueryHint.getGraph() );
|
||||
}
|
||||
}
|
||||
|
||||
protected void afterQuery() {
|
||||
@ -1447,6 +1450,7 @@ protected void afterQuery() {
|
||||
getProducer().setCacheMode( sessionCacheMode );
|
||||
sessionCacheMode = null;
|
||||
}
|
||||
getProducer().setFetchGraphLoadContext( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,7 +50,7 @@ public void testSubsequentSelectFromFind() {
|
||||
final Issue issue = session.find(
|
||||
Issue.class,
|
||||
1,
|
||||
Collections.singletonMap( GraphSemantic.FETCH.getJpaHintName(), graph )
|
||||
Collections.singletonMap( GraphSemantic.LOAD.getJpaHintName(), graph )
|
||||
);
|
||||
|
||||
assertTrue( Hibernate.isInitialized( issue ) );
|
||||
|
@ -0,0 +1,133 @@
|
||||
package org.hibernate.jpa.test.graphs.mapped_by_id;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.jpa.test.graphs.*;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.persistence.EntityGraph;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Subgraph;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
public class FetchGraphFindByIdTest extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
private long companyId;
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-8776")
|
||||
public void testFetchGraphByFind() {
|
||||
EntityManager entityManager = getOrCreateEntityManager();
|
||||
entityManager.getTransaction().begin();
|
||||
|
||||
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
|
||||
entityGraph.addAttributeNodes( "location" );
|
||||
entityGraph.addAttributeNodes( "markets" );
|
||||
|
||||
Map<String, Object> properties = Collections.singletonMap( "javax.persistence.fetchgraph", entityGraph );
|
||||
|
||||
Company company = entityManager.find( Company.class, companyId, properties );
|
||||
|
||||
entityManager.getTransaction().commit();
|
||||
entityManager.close();
|
||||
|
||||
assertFalse( Hibernate.isInitialized( company.employees ) );
|
||||
assertTrue( Hibernate.isInitialized( company.location ) );
|
||||
assertTrue( Hibernate.isInitialized( company.markets ) );
|
||||
// With "fetchgraph", non-specified attributes effect 'lazy' mode. So, here,
|
||||
// @ElementCollection(fetch = FetchType.EAGER) should not be initialized.
|
||||
assertFalse( Hibernate.isInitialized( company.phoneNumbers ) );
|
||||
|
||||
entityManager = getOrCreateEntityManager();
|
||||
entityManager.getTransaction().begin();
|
||||
|
||||
Subgraph<Employee> subgraph = entityGraph.addSubgraph( "employees" );
|
||||
subgraph.addAttributeNodes( "managers" );
|
||||
subgraph.addAttributeNodes( "friends" );
|
||||
Subgraph<Manager> subSubgraph = subgraph.addSubgraph( "managers", Manager.class );
|
||||
subSubgraph.addAttributeNodes( "managers" );
|
||||
subSubgraph.addAttributeNodes( "friends" );
|
||||
|
||||
company = entityManager.find( Company.class, companyId, properties );
|
||||
|
||||
entityManager.getTransaction().commit();
|
||||
entityManager.close();
|
||||
|
||||
assertTrue( Hibernate.isInitialized( company.employees ) );
|
||||
assertTrue( Hibernate.isInitialized( company.location ) );
|
||||
assertEquals( 12345, company.location.zip );
|
||||
assertTrue( Hibernate.isInitialized( company.markets ) );
|
||||
// With "fetchgraph", non-specified attributes effect 'lazy' mode. So, here,
|
||||
// @ElementCollection(fetch = FetchType.EAGER) should not be initialized.
|
||||
assertFalse( Hibernate.isInitialized( company.phoneNumbers ) );
|
||||
|
||||
boolean foundManager = false;
|
||||
Iterator<Employee> employeeItr = company.employees.iterator();
|
||||
while (employeeItr.hasNext()) {
|
||||
Employee employee = employeeItr.next();
|
||||
assertTrue( Hibernate.isInitialized( employee.managers ) );
|
||||
assertTrue( Hibernate.isInitialized( employee.friends ) );
|
||||
// test 1 more level
|
||||
Iterator<Manager> managerItr = employee.managers.iterator();
|
||||
while (managerItr.hasNext()) {
|
||||
foundManager = true;
|
||||
Manager manager = managerItr.next();
|
||||
assertTrue( Hibernate.isInitialized( manager.managers ) );
|
||||
assertTrue( Hibernate.isInitialized( manager.friends ) );
|
||||
}
|
||||
}
|
||||
assertTrue(foundManager);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void createData() {
|
||||
EntityManager entityManager = getOrCreateEntityManager();
|
||||
entityManager.getTransaction().begin();
|
||||
|
||||
Manager manager1 = new Manager();
|
||||
entityManager.persist( manager1 );
|
||||
|
||||
Manager manager2 = new Manager();
|
||||
manager2.managers.add( manager1 );
|
||||
entityManager.persist( manager2 );
|
||||
|
||||
Employee employee = new Employee();
|
||||
employee.managers.add( manager1 );
|
||||
entityManager.persist( employee );
|
||||
|
||||
Location location = new Location();
|
||||
location.address = "123 somewhere";
|
||||
location.zip = 12345;
|
||||
entityManager.persist( location );
|
||||
|
||||
Company company = new Company();
|
||||
company.employees.add( employee );
|
||||
company.employees.add( manager1 );
|
||||
company.employees.add( manager2 );
|
||||
company.location = location;
|
||||
company.markets.add( Market.SERVICES );
|
||||
company.markets.add( Market.TECHNOLOGY );
|
||||
company.phoneNumbers.add( "012-345-6789" );
|
||||
company.phoneNumbers.add( "987-654-3210" );
|
||||
entityManager.persist( company );
|
||||
companyId = company.id;
|
||||
|
||||
entityManager.getTransaction().commit();
|
||||
entityManager.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Company.class, Employee.class, Manager.class, Location.class, Course.class, Student.class };
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,6 @@
|
||||
import javax.persistence.EntityGraph;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MapsId;
|
||||
import javax.persistence.OneToOne;
|
||||
|
@ -6,7 +6,6 @@
|
||||
*/
|
||||
package org.hibernate.jpa.test.graphs.queryhint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -34,18 +33,14 @@
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Brett Meyer
|
||||
* @author Nathan Xu
|
||||
*/
|
||||
public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
// TODO: Currently, "loadgraph" and "fetchgraph" operate identically in JPQL. The spec states that "fetchgraph"
|
||||
// shall use LAZY for non-specified attributes, ignoring their metadata. Changes to ToOne select vs. join,
|
||||
// allowing queries to force laziness, etc. will require changes here and impl logic.
|
||||
|
||||
@Test
|
||||
public void testLoadGraph() {
|
||||
EntityManager entityManager = getOrCreateEntityManager();
|
||||
@ -111,6 +106,72 @@ public void testLoadGraph() {
|
||||
assertTrue(foundManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-8776")
|
||||
public void testFetchGraph() {
|
||||
EntityManager entityManager = getOrCreateEntityManager();
|
||||
entityManager.getTransaction().begin();
|
||||
|
||||
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
|
||||
entityGraph.addAttributeNodes( "location" );
|
||||
entityGraph.addAttributeNodes( "markets" );
|
||||
Query query = entityManager.createQuery( "from " + Company.class.getName() );
|
||||
query.setHint( QueryHints.HINT_FETCHGRAPH, entityGraph );
|
||||
Company company = (Company) query.getSingleResult();
|
||||
|
||||
entityManager.getTransaction().commit();
|
||||
entityManager.close();
|
||||
|
||||
assertFalse( Hibernate.isInitialized( company.employees ) );
|
||||
assertTrue( Hibernate.isInitialized( company.location ) );
|
||||
assertTrue( Hibernate.isInitialized( company.markets ) );
|
||||
// With "fetchgraph", non-specified attributes effect 'lazy' mode. So, here,
|
||||
// @ElementCollection(fetch = FetchType.EAGER) should not be initialized.
|
||||
assertFalse( Hibernate.isInitialized( company.phoneNumbers ) );
|
||||
|
||||
entityManager = getOrCreateEntityManager();
|
||||
entityManager.getTransaction().begin();
|
||||
|
||||
Subgraph<Employee> subgraph = entityGraph.addSubgraph( "employees" );
|
||||
subgraph.addAttributeNodes( "managers" );
|
||||
subgraph.addAttributeNodes( "friends" );
|
||||
Subgraph<Manager> subSubgraph = subgraph.addSubgraph( "managers", Manager.class );
|
||||
subSubgraph.addAttributeNodes( "managers" );
|
||||
subSubgraph.addAttributeNodes( "friends" );
|
||||
|
||||
query = entityManager.createQuery( "from " + Company.class.getName() );
|
||||
query.setHint( QueryHints.HINT_FETCHGRAPH, entityGraph );
|
||||
company = (Company) query.getSingleResult();
|
||||
|
||||
entityManager.getTransaction().commit();
|
||||
entityManager.close();
|
||||
|
||||
assertTrue( Hibernate.isInitialized( company.employees ) );
|
||||
assertTrue( Hibernate.isInitialized( company.location ) );
|
||||
assertEquals( 12345, company.location.zip );
|
||||
assertTrue( Hibernate.isInitialized( company.markets ) );
|
||||
// With "fetchgraph", non-specified attributes effect 'lazy' mode. So, here,
|
||||
// @ElementCollection(fetch = FetchType.EAGER) should not be initialized.
|
||||
assertFalse( Hibernate.isInitialized( company.phoneNumbers ) );
|
||||
|
||||
boolean foundManager = false;
|
||||
Iterator<Employee> employeeItr = company.employees.iterator();
|
||||
while (employeeItr.hasNext()) {
|
||||
Employee employee = employeeItr.next();
|
||||
assertTrue( Hibernate.isInitialized( employee.managers ) );
|
||||
assertTrue( Hibernate.isInitialized( employee.friends ) );
|
||||
// test 1 more level
|
||||
Iterator<Manager> managerItr = employee.managers.iterator();
|
||||
while (managerItr.hasNext()) {
|
||||
foundManager = true;
|
||||
Manager manager = managerItr.next();
|
||||
assertTrue( Hibernate.isInitialized( manager.managers ) );
|
||||
assertTrue( Hibernate.isInitialized( manager.friends ) );
|
||||
}
|
||||
}
|
||||
assertTrue(foundManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-9457")
|
||||
public void testLoadGraphOrderByWithImplicitJoin() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user