HHH-13175 - Eager subsequent-select fails when EntityGraph is specified for find operation
This commit is contained in:
parent
99abb99edf
commit
858524cd27
|
@ -139,7 +139,12 @@ public abstract class AbstractGraph<J> extends AbstractGraphNode<J> implements G
|
|||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <AJ> AttributeNodeImplementor<AJ> findAttributeNode(String attributeName) {
|
||||
return findAttributeNode( (PersistentAttributeDescriptor) managedType.getAttribute( attributeName ) );
|
||||
final PersistentAttributeDescriptor<? super J, ?> attribute = managedType.findAttribute( attributeName );
|
||||
if ( attribute == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return findAttributeNode( (PersistentAttributeDescriptor) attribute );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -119,6 +119,20 @@ public abstract class AbstractManagedType<J>
|
|||
return attribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentAttributeDescriptor<J, ?> findDeclaredAttribute(String name) {
|
||||
return declaredAttributes.get( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentAttributeDescriptor<? super J, ?> findAttribute(String name) {
|
||||
PersistentAttributeDescriptor<? super J, ?> attribute = findDeclaredAttribute( name );
|
||||
if ( attribute == null && getSuperType() != null ) {
|
||||
attribute = getSuperType().findAttribute( name );
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentAttributeDescriptor<J, ?> getDeclaredAttribute(String name) {
|
||||
PersistentAttributeDescriptor<J, ?> attr = declaredAttributes.get( name );
|
||||
|
|
|
@ -80,6 +80,9 @@ public interface ManagedTypeDescriptor<J> extends SimpleTypeDescriptor<J>, Manag
|
|||
void finishUp();
|
||||
}
|
||||
|
||||
PersistentAttributeDescriptor<J, ?> findDeclaredAttribute(String name);
|
||||
|
||||
PersistentAttributeDescriptor<? super J, ?> findAttribute(String name);
|
||||
|
||||
@Override
|
||||
PersistentAttributeDescriptor<J, ?> getDeclaredAttribute(String name);
|
||||
|
|
|
@ -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<Issue> 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<Comment> 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<Comment> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
public void setComments(List<Comment> 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<Issue> 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<Issue> getAssignedIssues() {
|
||||
return assignedIssues;
|
||||
}
|
||||
|
||||
public void setAssignedIssues(Set<Issue> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,7 @@ public class GraphParsingTestEntity {
|
|||
private String description;
|
||||
|
||||
private GraphParsingTestEntity linkToOne;
|
||||
private GraphParsingTestEntity linkToOneLazy;
|
||||
|
||||
private Map<GraphParsingTestEntity, GraphParsingTestEntity> 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<GraphParsingTestEntity, GraphParsingTestEntity> getMap() {
|
||||
|
|
Loading…
Reference in New Issue