HHH-14212 fix Fetch Graph by simply returning false in TwoPhaseLoad#getOverridingEager() when Fetch Graph is being enforced
This commit is contained in:
parent
39b42c0a6a
commit
99a4edfac0
|
@ -406,7 +406,11 @@ public final class TwoPhaseLoad {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if eager of the association is overridden by anything.
|
* Check if eager of the association is overridden (i.e. skipping metamodel strategy), including (order sensitive):
|
||||||
|
* <ol>
|
||||||
|
* <li>fetch graph</li>
|
||||||
|
* <li>fetch profile</li>
|
||||||
|
* </ol>
|
||||||
*
|
*
|
||||||
* @param session session
|
* @param session session
|
||||||
* @param entityName entity name
|
* @param entityName entity name
|
||||||
|
@ -424,6 +428,12 @@ public final class TwoPhaseLoad {
|
||||||
// Performance: check type.isCollectionType() first, as type.isAssociationType() is megamorphic
|
// Performance: check type.isCollectionType() first, as type.isAssociationType() is megamorphic
|
||||||
if ( associationType.isCollectionType() || associationType.isAssociationType() ) {
|
if ( associationType.isCollectionType() || associationType.isAssociationType() ) {
|
||||||
|
|
||||||
|
// we can return false invariably for if the entity has been covered by entity graph,
|
||||||
|
// its associated JOIN has been present in the SQL generated and hence it would be loaded anyway
|
||||||
|
if ( session.isEnforcingFetchGraph() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// check 'fetch profile' next; skip 'metamodel' if 'fetch profile' takes effect
|
// check 'fetch profile' next; skip 'metamodel' if 'fetch profile' takes effect
|
||||||
final Boolean overridingEager = isEagerFetchProfile( session, entityName, associationName );
|
final Boolean overridingEager = isEagerFetchProfile( session, entityName, associationName );
|
||||||
|
|
||||||
|
|
|
@ -522,4 +522,11 @@ public interface SharedSessionContractImplementor
|
||||||
*/
|
*/
|
||||||
PersistenceContext getPersistenceContextInternal();
|
PersistenceContext getPersistenceContextInternal();
|
||||||
|
|
||||||
|
default boolean isEnforcingFetchGraph() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setEnforcingFetchGraph(boolean enforcingFetchGraph) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,6 +215,8 @@ public class SessionImpl
|
||||||
|
|
||||||
private transient TransactionObserver transactionObserver;
|
private transient TransactionObserver transactionObserver;
|
||||||
|
|
||||||
|
private transient boolean isEnforcingFetchGraph;
|
||||||
|
|
||||||
public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
|
public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
|
||||||
super( factory, options );
|
super( factory, options );
|
||||||
|
|
||||||
|
@ -3313,6 +3315,10 @@ public class SessionImpl
|
||||||
loadAccess.with( lockOptions );
|
loadAccess.with( lockOptions );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( getLoadQueryInfluencers().getEffectiveEntityGraph().getSemantic() == GraphSemantic.FETCH ) {
|
||||||
|
setEnforcingFetchGraph( true );
|
||||||
|
}
|
||||||
|
|
||||||
return loadAccess.load( (Serializable) primaryKey );
|
return loadAccess.load( (Serializable) primaryKey );
|
||||||
}
|
}
|
||||||
catch ( EntityNotFoundException ignored ) {
|
catch ( EntityNotFoundException ignored ) {
|
||||||
|
@ -3357,6 +3363,7 @@ public class SessionImpl
|
||||||
finally {
|
finally {
|
||||||
getLoadQueryInfluencers().getEffectiveEntityGraph().clear();
|
getLoadQueryInfluencers().getEffectiveEntityGraph().clear();
|
||||||
getLoadQueryInfluencers().setReadOnly( null );
|
getLoadQueryInfluencers().setReadOnly( null );
|
||||||
|
setEnforcingFetchGraph( false );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3785,4 +3792,15 @@ public class SessionImpl
|
||||||
}
|
}
|
||||||
return readOnly;
|
return readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnforcingFetchGraph() {
|
||||||
|
return this.isEnforcingFetchGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnforcingFetchGraph(boolean isEnforcingFetchGraph) {
|
||||||
|
this.isEnforcingFetchGraph = isEnforcingFetchGraph;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1437,6 +1437,9 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
||||||
sessionCacheMode = getProducer().getCacheMode();
|
sessionCacheMode = getProducer().getCacheMode();
|
||||||
getProducer().setCacheMode( effectiveCacheMode );
|
getProducer().setCacheMode( effectiveCacheMode );
|
||||||
}
|
}
|
||||||
|
if ( entityGraphQueryHint != null && entityGraphQueryHint.getSemantic() == GraphSemantic.FETCH ) {
|
||||||
|
getProducer().setEnforcingFetchGraph( true );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void afterQuery() {
|
protected void afterQuery() {
|
||||||
|
@ -1448,6 +1451,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
||||||
getProducer().setCacheMode( sessionCacheMode );
|
getProducer().setCacheMode( sessionCacheMode );
|
||||||
sessionCacheMode = null;
|
sessionCacheMode = null;
|
||||||
}
|
}
|
||||||
|
getProducer().setEnforcingFetchGraph( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
package org.hibernate.jpa.test.graphs;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.annotations.Fetch;
|
||||||
|
import org.hibernate.annotations.FetchMode;
|
||||||
|
import org.hibernate.graph.GraphParser;
|
||||||
|
import org.hibernate.graph.GraphSemantic;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Yaroslav Prokipchyn
|
||||||
|
* @author Nathan Xu
|
||||||
|
*/
|
||||||
|
@TestForIssue( jiraKey = "HHH-14212" )
|
||||||
|
public class FetchGraphTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
LedgerRecord.class,
|
||||||
|
LedgerRecordItem.class,
|
||||||
|
BudgetRecord.class,
|
||||||
|
Trigger.class,
|
||||||
|
FinanceEntity.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Trigger trigger = new Trigger();
|
||||||
|
entityManager.persist( trigger );
|
||||||
|
|
||||||
|
BudgetRecord budgetRecord = new BudgetRecord();
|
||||||
|
budgetRecord.amount = 100;
|
||||||
|
budgetRecord.trigger = trigger;
|
||||||
|
entityManager.persist( budgetRecord );
|
||||||
|
|
||||||
|
FinanceEntity client = new FinanceEntity();
|
||||||
|
client.name = "client";
|
||||||
|
FinanceEntity vendor = new FinanceEntity();
|
||||||
|
vendor.name = "vendor";
|
||||||
|
entityManager.persist( client );
|
||||||
|
entityManager.persist( vendor );
|
||||||
|
|
||||||
|
LedgerRecordItem item1 = new LedgerRecordItem();
|
||||||
|
item1.financeEntity = client;
|
||||||
|
LedgerRecordItem item2 = new LedgerRecordItem();
|
||||||
|
item2.financeEntity = vendor;
|
||||||
|
entityManager.persist( item1 );
|
||||||
|
entityManager.persist( item2 );
|
||||||
|
|
||||||
|
LedgerRecord ledgerRecord = new LedgerRecord();
|
||||||
|
ledgerRecord.budgetRecord = budgetRecord;
|
||||||
|
ledgerRecord.trigger = trigger;
|
||||||
|
ledgerRecord.ledgerRecordItems= Arrays.asList( item1, item2 );
|
||||||
|
|
||||||
|
item1.ledgerRecord = ledgerRecord;
|
||||||
|
item2.ledgerRecord = ledgerRecord;
|
||||||
|
|
||||||
|
entityManager.persist( ledgerRecord );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCollectionEntityGraph() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
final EntityGraph<LedgerRecord> entityGraph = GraphParser.parse( LedgerRecord.class, "budgetRecord, ledgerRecordItems.value(financeEntity)", entityManager );
|
||||||
|
final List<LedgerRecord> records = entityManager.createQuery( "from LedgerRecord", LedgerRecord.class )
|
||||||
|
.setHint( GraphSemantic.FETCH.getJpaHintName(), entityGraph )
|
||||||
|
.getResultList();
|
||||||
|
assertThat( records.size(), is( 1 ) );
|
||||||
|
records.forEach( record -> {
|
||||||
|
assertFalse( Hibernate.isInitialized( record.trigger ) );
|
||||||
|
assertTrue( Hibernate.isInitialized( record.budgetRecord ) );
|
||||||
|
assertFalse( Hibernate.isInitialized( record.budgetRecord.trigger ) );
|
||||||
|
assertTrue( Hibernate.isInitialized( record.ledgerRecordItems) );
|
||||||
|
assertThat( record.ledgerRecordItems.size(), is( 2 ) );
|
||||||
|
record.ledgerRecordItems.forEach( item -> {
|
||||||
|
assertSame( record, item.ledgerRecord );
|
||||||
|
assertTrue( Hibernate.isInitialized( item.financeEntity ) );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "LedgerRecord")
|
||||||
|
@Table(name = "LedgerRecord")
|
||||||
|
static class LedgerRecord {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Integer id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
BudgetRecord budgetRecord;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "ledgerRecord", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||||
|
@Fetch(FetchMode.SUBSELECT)
|
||||||
|
List<LedgerRecordItem> ledgerRecordItems;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
Trigger trigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "LedgerRecordItem")
|
||||||
|
@Table(name = "LedgerRecordItem")
|
||||||
|
static class LedgerRecordItem {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Integer id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
LedgerRecord ledgerRecord;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
FinanceEntity financeEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "BudgetRecord")
|
||||||
|
@Table(name = "BudgetRecord")
|
||||||
|
static class BudgetRecord {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Integer id;
|
||||||
|
|
||||||
|
int amount;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
Trigger trigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Trigger")
|
||||||
|
@Table(name = "Trigger")
|
||||||
|
static class Trigger {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Integer id;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "FinanceEntity")
|
||||||
|
@Table(name = "FinanceEntity")
|
||||||
|
static class FinanceEntity {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
Integer id;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue