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
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <AJ> AttributeNodeImplementor<AJ> findAttributeNode(String attributeName) {
|
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
|
@Override
|
||||||
|
|
|
@ -86,6 +86,7 @@ import org.hibernate.engine.query.spi.NativeSQLQueryPlan;
|
||||||
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
|
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
|
||||||
import org.hibernate.engine.spi.ActionQueue;
|
import org.hibernate.engine.spi.ActionQueue;
|
||||||
import org.hibernate.engine.spi.CollectionEntry;
|
import org.hibernate.engine.spi.CollectionEntry;
|
||||||
|
import org.hibernate.engine.spi.EffectiveEntityGraph;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
|
@ -1128,31 +1129,57 @@ public final class SessionImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable)
|
public final Object internalLoad(
|
||||||
throws HibernateException {
|
String entityName,
|
||||||
// todo : remove
|
Serializable id,
|
||||||
LoadEventListener.LoadType type = nullable
|
boolean eager,
|
||||||
? LoadEventListener.INTERNAL_LOAD_NULLABLE
|
boolean nullable) throws HibernateException {
|
||||||
: eager
|
|
||||||
? LoadEventListener.INTERNAL_LOAD_EAGER
|
|
||||||
: LoadEventListener.INTERNAL_LOAD_LAZY;
|
|
||||||
|
|
||||||
LoadEvent event = loadEvent;
|
final EffectiveEntityGraph effectiveEntityGraph = getLoadQueryInfluencers().getEffectiveEntityGraph();
|
||||||
loadEvent = null;
|
final GraphSemantic semantic = effectiveEntityGraph.getSemantic();
|
||||||
event = recycleEventInstance( event, id, entityName );
|
final RootGraphImplementor<?> graph = effectiveEntityGraph.getGraph();
|
||||||
fireLoad( event, type );
|
boolean clearedEffectiveGraph = false;
|
||||||
Object result = event.getResult();
|
if ( semantic != null ) {
|
||||||
if ( !nullable ) {
|
if ( ! graph.appliesTo( entityName ) ) {
|
||||||
UnresolvableObjectException.throwIfNull( result, id, entityName );
|
log.debugf( "Clearing effective entity graph for subsequent-select" );
|
||||||
|
clearedEffectiveGraph = true;
|
||||||
|
effectiveEntityGraph.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ( loadEvent == null ) {
|
|
||||||
event.setEntityClassName( null );
|
try {
|
||||||
event.setEntityId( null );
|
final LoadEventListener.LoadType type;
|
||||||
event.setInstanceToLoad( null );
|
if ( nullable ) {
|
||||||
event.setResult( null );
|
type = LoadEventListener.INTERNAL_LOAD_NULLABLE;
|
||||||
loadEvent = event;
|
}
|
||||||
|
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;
|
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
|
@Override
|
||||||
public PersistentAttributeDescriptor<J, ?> getDeclaredAttribute(String name) {
|
public PersistentAttributeDescriptor<J, ?> getDeclaredAttribute(String name) {
|
||||||
PersistentAttributeDescriptor<J, ?> attr = declaredAttributes.get( name );
|
PersistentAttributeDescriptor<J, ?> attr = declaredAttributes.get( name );
|
||||||
|
|
|
@ -80,6 +80,9 @@ public interface ManagedTypeDescriptor<J> extends SimpleTypeDescriptor<J>, Manag
|
||||||
void finishUp();
|
void finishUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PersistentAttributeDescriptor<J, ?> findDeclaredAttribute(String name);
|
||||||
|
|
||||||
|
PersistentAttributeDescriptor<? super J, ?> findAttribute(String name);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
PersistentAttributeDescriptor<J, ?> getDeclaredAttribute(String name);
|
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 String description;
|
||||||
|
|
||||||
private GraphParsingTestEntity linkToOne;
|
private GraphParsingTestEntity linkToOne;
|
||||||
|
private GraphParsingTestEntity linkToOneLazy;
|
||||||
|
|
||||||
private Map<GraphParsingTestEntity, GraphParsingTestEntity> map;
|
private Map<GraphParsingTestEntity, GraphParsingTestEntity> map;
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ public class GraphParsingTestEntity {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne( fetch = FetchType.EAGER )
|
||||||
public GraphParsingTestEntity getLinkToOne() {
|
public GraphParsingTestEntity getLinkToOne() {
|
||||||
return linkToOne;
|
return linkToOne;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +52,15 @@ public class GraphParsingTestEntity {
|
||||||
this.linkToOne = linkToOne;
|
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)
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
@ElementCollection(targetClass = GraphParsingTestEntity.class)
|
@ElementCollection(targetClass = GraphParsingTestEntity.class)
|
||||||
public Map<GraphParsingTestEntity, GraphParsingTestEntity> getMap() {
|
public Map<GraphParsingTestEntity, GraphParsingTestEntity> getMap() {
|
||||||
|
|
Loading…
Reference in New Issue