From 858524cd270c30144995624e22088003cf8d6703 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 31 Dec 2018 08:55:31 -0600 Subject: [PATCH] HHH-13175 - Eager subsequent-select fails when EntityGraph is specified for find operation --- .../graph/internal/AbstractGraph.java | 7 +- .../org/hibernate/internal/SessionImpl.java | 71 ++-- .../domain/internal/AbstractManagedType.java | 14 + .../domain/spi/ManagedTypeDescriptor.java | 3 + .../graph/EntityGraphFunctionalTests.java | 306 ++++++++++++++++++ .../graph/GraphParsingTestEntity.java | 12 +- 6 files changed, 389 insertions(+), 24 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/graph/EntityGraphFunctionalTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/AbstractGraph.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/AbstractGraph.java index 6c070eee09..d5508d5ce5 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/AbstractGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/AbstractGraph.java @@ -139,7 +139,12 @@ public abstract class AbstractGraph extends AbstractGraphNode implements G @Override @SuppressWarnings("unchecked") public AttributeNodeImplementor findAttributeNode(String attributeName) { - return findAttributeNode( (PersistentAttributeDescriptor) managedType.getAttribute( attributeName ) ); + final PersistentAttributeDescriptor attribute = managedType.findAttribute( attributeName ); + if ( attribute == null ) { + return null; + } + + return findAttributeNode( (PersistentAttributeDescriptor) attribute ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index e52e6060de..de4e20db6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -86,6 +86,7 @@ import org.hibernate.engine.query.spi.NativeSQLQueryPlan; import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -1128,31 +1129,57 @@ public final class SessionImpl } @Override - public final Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) - throws HibernateException { - // todo : remove - LoadEventListener.LoadType type = nullable - ? LoadEventListener.INTERNAL_LOAD_NULLABLE - : eager - ? LoadEventListener.INTERNAL_LOAD_EAGER - : LoadEventListener.INTERNAL_LOAD_LAZY; + public final Object internalLoad( + String entityName, + Serializable id, + boolean eager, + boolean nullable) throws HibernateException { - LoadEvent event = loadEvent; - loadEvent = null; - event = recycleEventInstance( event, id, entityName ); - fireLoad( event, type ); - Object result = event.getResult(); - if ( !nullable ) { - UnresolvableObjectException.throwIfNull( result, id, entityName ); + final EffectiveEntityGraph effectiveEntityGraph = getLoadQueryInfluencers().getEffectiveEntityGraph(); + final GraphSemantic semantic = effectiveEntityGraph.getSemantic(); + final RootGraphImplementor graph = effectiveEntityGraph.getGraph(); + boolean clearedEffectiveGraph = false; + if ( semantic != null ) { + if ( ! graph.appliesTo( entityName ) ) { + log.debugf( "Clearing effective entity graph for subsequent-select" ); + clearedEffectiveGraph = true; + effectiveEntityGraph.clear(); + } } - if ( loadEvent == null ) { - event.setEntityClassName( null ); - event.setEntityId( null ); - event.setInstanceToLoad( null ); - event.setResult( null ); - loadEvent = event; + + try { + final LoadEventListener.LoadType type; + if ( nullable ) { + type = LoadEventListener.INTERNAL_LOAD_NULLABLE; + } + else { + type = eager + ? LoadEventListener.INTERNAL_LOAD_EAGER + : LoadEventListener.INTERNAL_LOAD_LAZY; + } + + LoadEvent event = loadEvent; + loadEvent = null; + event = recycleEventInstance( event, id, entityName ); + fireLoad( event, type ); + Object result = event.getResult(); + if ( !nullable ) { + UnresolvableObjectException.throwIfNull( result, id, entityName ); + } + if ( loadEvent == null ) { + event.setEntityClassName( null ); + event.setEntityId( null ); + event.setInstanceToLoad( null ); + event.setResult( null ); + loadEvent = event; + } + return result; + } + finally { + if ( clearedEffectiveGraph ) { + effectiveEntityGraph.applyGraph( graph, semantic ); + } } - return result; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java index 37557aa762..89f0d4bca2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractManagedType.java @@ -119,6 +119,20 @@ public abstract class AbstractManagedType return attribute; } + @Override + public PersistentAttributeDescriptor findDeclaredAttribute(String name) { + return declaredAttributes.get( name ); + } + + @Override + public PersistentAttributeDescriptor findAttribute(String name) { + PersistentAttributeDescriptor attribute = findDeclaredAttribute( name ); + if ( attribute == null && getSuperType() != null ) { + attribute = getSuperType().findAttribute( name ); + } + return attribute; + } + @Override public PersistentAttributeDescriptor getDeclaredAttribute(String name) { PersistentAttributeDescriptor attr = declaredAttributes.get( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/spi/ManagedTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/spi/ManagedTypeDescriptor.java index d7a34817de..e7df9609ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/spi/ManagedTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/spi/ManagedTypeDescriptor.java @@ -80,6 +80,9 @@ public interface ManagedTypeDescriptor extends SimpleTypeDescriptor, Manag void finishUp(); } + PersistentAttributeDescriptor findDeclaredAttribute(String name); + + PersistentAttributeDescriptor findAttribute(String name); @Override PersistentAttributeDescriptor getDeclaredAttribute(String name); diff --git a/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphFunctionalTests.java b/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphFunctionalTests.java new file mode 100644 index 0000000000..52a2545b93 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/graph/EntityGraphFunctionalTests.java @@ -0,0 +1,306 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.graph; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Hibernate; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +public class EntityGraphFunctionalTests extends BaseEntityManagerFunctionalTestCase { + + @Test + @TestForIssue( jiraKey = "HHH-13175") + public void testSubsequentSelectFromFind() { + inTransaction( + entityManagerFactory(), + session -> { + final RootGraph graph = GraphParser.parse( Issue.class, "comments", session ); + + final Issue issue = session.find( + Issue.class, + 1, + Collections.singletonMap( GraphSemantic.FETCH.getJpaHintName(), graph ) + ); + + assertTrue( Hibernate.isInitialized( issue ) ); + assertTrue( Hibernate.isInitialized( issue.getComments() ) ); + assertTrue( Hibernate.isInitialized( issue.getReporter() ) ); + assertTrue( Hibernate.isInitialized( issue.getAssignee() ) ); + + assertFalse( Hibernate.isInitialized( issue.getAssignee().getAssignedIssues() ) ); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { User.class, Issue.class, Comment.class }; + } + + @Override + protected boolean createSchema() { + return true; + } + + @Before + public void prepareTestData() { + inTransaction( + entityManagerFactory(), + session -> { + final User wesley = new User( "Wesley", "farmboy" ); + final User buttercup = new User( "Buttercup", "wishee" ); + final User humperdink = new User( "Humperdink", "buffoon" ); + + session.save( wesley ); + session.save( buttercup ); + session.save( humperdink ); + + final Issue flameSpurt = new Issue( 1, "Flame Spurt", wesley ); + final Issue lightningSand = new Issue( 2, "Lightning Sand", buttercup ); + final Issue rous = new Issue( 3, "Rodents of Unusual Size", wesley ); + + flameSpurt.setAssignee( wesley ); + lightningSand.setAssignee( wesley ); + rous.setAssignee( wesley ); + + session.save( flameSpurt ); + session.save( lightningSand ); + session.save( rous ); + + flameSpurt.addComment( "There is a popping sound preceding each", wesley ); + rous.addComment( "I don't think they exist", wesley ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + entityManagerFactory(), + session -> { + session.createQuery( "delete from Comment" ).executeUpdate(); + session.createQuery( "delete from Issue" ).executeUpdate(); + session.createQuery( "delete from User" ).executeUpdate(); + } + ); + } + + @Entity( name = "Issue") + @Table( name = "issue" ) + public static class Issue { + private Integer id; + private String description; + private User reporter; + private User assignee; + private List comments; + + public Issue() { + } + + public Issue(Integer id, String description, User reporter) { + this.id = id; + this.description = description; + this.reporter = reporter; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @ManyToOne + public User getReporter() { + return reporter; + } + + public void setReporter(User reporter) { + this.reporter = reporter; + } + + @ManyToOne + public User getAssignee() { + return assignee; + } + + public void setAssignee(User assignee) { + this.assignee = assignee; + } + + @OneToMany( mappedBy = "issue", cascade = CascadeType.ALL) + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } + + public void addComment(String comment, User user) { + if ( comments == null ) { + comments = new ArrayList<>(); + } + + comments.add( new Comment( this, comment, user ) ); + } + } + + @Entity( name = "User") + @Table( name = "`user`" ) + public static class User { + private Integer id; + private String name; + private String login; + private Set assignedIssues; + + public User() { + } + + public User(String name, String login) { + this.name = name; + this.login = login; + } + + @Id + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(mappedBy="assignee", fetch= FetchType.LAZY) + public Set getAssignedIssues() { + return assignedIssues; + } + + public void setAssignedIssues(Set assignedIssues) { + this.assignedIssues = assignedIssues; + } + } + + @Entity( name = "Comment") + @Table( name = "comment" ) + public static class Comment { + private Integer id; + private Issue issue; + private String text; + private Instant addedOn; + private User commenter; + + public Comment() { + } + + public Comment( + Issue issue, + String text, + User commenter) { + this.id = id; + this.issue = issue; + this.text = text; + this.commenter = commenter; + + this.addedOn = Instant.now(); + } + + @Id + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn(name="issue_id", nullable=false) + public Issue getIssue() { + return issue; + } + + public void setIssue(Issue issue) { + this.issue = issue; + } + + @Column( name = "`text`" ) + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Instant getAddedOn() { + return addedOn; + } + + public void setAddedOn(Instant addedOn) { + this.addedOn = addedOn; + } + + @ManyToOne + @JoinColumn(name="user_id", nullable=false) + public User getCommenter() { + return commenter; + } + + public void setCommenter(User commenter) { + this.commenter = commenter; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/graph/GraphParsingTestEntity.java b/hibernate-core/src/test/java/org/hibernate/graph/GraphParsingTestEntity.java index 634673a142..02e7f5f9f3 100644 --- a/hibernate-core/src/test/java/org/hibernate/graph/GraphParsingTestEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/graph/GraphParsingTestEntity.java @@ -21,6 +21,7 @@ public class GraphParsingTestEntity { private String description; private GraphParsingTestEntity linkToOne; + private GraphParsingTestEntity linkToOneLazy; private Map map; @@ -42,7 +43,7 @@ public class GraphParsingTestEntity { this.name = name; } - @ManyToOne + @ManyToOne( fetch = FetchType.EAGER ) public GraphParsingTestEntity getLinkToOne() { return linkToOne; } @@ -51,6 +52,15 @@ public class GraphParsingTestEntity { this.linkToOne = linkToOne; } + @ManyToOne( fetch = FetchType.LAZY ) + public GraphParsingTestEntity getLinkToOneLazy() { + return linkToOneLazy; + } + + public void setLinkToOneLazy(GraphParsingTestEntity linkToOneLazy) { + this.linkToOneLazy = linkToOneLazy; + } + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @ElementCollection(targetClass = GraphParsingTestEntity.class) public Map getMap() {