From a23a9a81e3b32bb79ce1a11e280b97c525d4db27 Mon Sep 17 00:00:00 2001 From: Harsh Panchal Date: Tue, 14 Mar 2017 09:38:00 +0530 Subject: [PATCH] HHH-11569 - Return only distinct elements when query is hinted with EntityGraph --- .../hql/internal/ast/QueryTranslatorImpl.java | 6 +- .../org/hibernate/jpa/test/graphs/Course.java | 90 +++++++++++++++++++ .../hibernate/jpa/test/graphs/Student.java | 84 +++++++++++++++++ .../queryhint/QueryHintEntityGraphTest.java | 58 ++++++++++-- 4 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java index c5ff3a16be..2d229d00b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/QueryTranslatorImpl.java @@ -358,7 +358,11 @@ public class QueryTranslatorImpl implements FilterTranslator { final QueryNode query = (QueryNode) sqlAst; final boolean hasLimit = queryParameters.getRowSelection() != null && queryParameters.getRowSelection().definesLimits(); - final boolean needsDistincting = ( query.getSelectClause().isDistinct() || hasLimit ) && containsCollectionFetches(); + final boolean needsDistincting = ( + query.getSelectClause().isDistinct() || + getEntityGraphQueryHint() != null || + hasLimit ) + && containsCollectionFetches(); QueryParameters queryParametersToUse; if ( hasLimit && containsCollectionFetches() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java new file mode 100644 index 0000000000..de964b154b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Course.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.graphs; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Entity +@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) +public class Course { + @Id + @GeneratedValue + private int id; + + private String name; + + @ManyToMany(mappedBy="courses", cascade=CascadeType.ALL) + private List students = new ArrayList<>(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getStudents() { + return students; + } + + public void setStudents(List students) { + this.students = students; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getId(); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + int id = getId(); + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Course other = (Course) obj; + if (id != other.id) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + @Override + public String toString() { + return "Course [name=" + name + "]"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java new file mode 100644 index 0000000000..e1f50b8b49 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/Student.java @@ -0,0 +1,84 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.graphs; + +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.UniqueConstraint; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +@Entity +@Cache(usage=CacheConcurrencyStrategy.READ_WRITE) +@NamedEntityGraphs({ + @NamedEntityGraph( + name = "Student.Full", + attributeNodes = { + @NamedAttributeNode(value = "courses") + } + ) +}) +@NamedQueries({ + @NamedQuery(name="LIST_OF_STD", query="select std from Student std") +}) +public class Student { + @Id + private int id; + + private String name; + + @ManyToMany(cascade=CascadeType.PERSIST) + @JoinTable( + name="STUDENT_COURSES", + joinColumns=@JoinColumn(referencedColumnName="ID", name="STUDENT_ID"), + inverseJoinColumns=@JoinColumn(referencedColumnName="ID", name="COURSE_ID"), + uniqueConstraints={@UniqueConstraint(columnNames={"STUDENT_ID", "COURSE_ID"})} + ) + private Set courses; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Set getCourses() { + return courses; + } + + public void setCourses(Set courses) { + this.courses = courses; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Student [name=" + name + "]"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java index 00504adbd6..06665aa17a 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/queryhint/QueryHintEntityGraphTest.java @@ -6,8 +6,11 @@ */ package org.hibernate.jpa.test.graphs.queryhint; +import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.Query; @@ -17,15 +20,18 @@ import org.hibernate.Hibernate; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.graphs.Company; +import org.hibernate.jpa.test.graphs.Course; import org.hibernate.jpa.test.graphs.Employee; import org.hibernate.jpa.test.graphs.Location; import org.hibernate.jpa.test.graphs.Manager; import org.hibernate.jpa.test.graphs.Market; +import org.hibernate.jpa.test.graphs.Student; import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; +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; @@ -133,11 +139,9 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph ); List results = query.getResultList(); - // we expect 3 results: // - 1st will be the Company with location.zip == 11234 with an empty markets collection - // - 2nd and 3rd should be the Company with location.zip == 12345 - // (2nd and 3rd are duplicated because that entity has 2 elements in markets collection - assertEquals( 3, results.size() ); + // - 2nd should be the Company with location.zip == 12345 + assertEquals( 2, results.size() ); Company companyResult = (Company) results.get( 0 ); assertFalse( Hibernate.isInitialized( companyResult.employees ) ); @@ -167,8 +171,6 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas assertTrue( Hibernate.isInitialized( companyResult.phoneNumbers ) ); assertEquals( 2, companyResult.phoneNumbers.size() ); - assertSame( companyResult, results.get( 2 ) ); - entityManager.getTransaction().commit(); entityManager.close(); } @@ -310,6 +312,48 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas entityManager.close(); } + @Test + @TestForIssue(jiraKey = "HHH-11569") + public void testCollectionSizeLoadedWithGraph() { + doInJPA( this::entityManagerFactory, entityManager -> { + + Student student1 = new Student(); + student1.setId( 1 ); + student1.setName( "Student 1" ); + Student student2 = new Student(); + student2.setId( 2 ); + student2.setName( "Student 2" ); + + Course course1 = new Course(); + course1.setName( "Full Time" ); + Course course2 = new Course(); + course2.setName( "Part Time" ); + + Set std1Courses = new HashSet(); + std1Courses.add( course1 ); + std1Courses.add( course2 ); + student1.setCourses( std1Courses ); + + Set std2Courses = new HashSet(); + std2Courses.add( course2 ); + student2.setCourses( std2Courses ); + + entityManager.persist( student1 ); + entityManager.persist( student2 ); + + }); + + doInJPA( this::entityManagerFactory, entityManager -> { + EntityGraph graph = entityManager.getEntityGraph( "Student.Full" ); + + List students = entityManager.createNamedQuery( "LIST_OF_STD", Student.class ) + .setHint( QueryHints.HINT_FETCHGRAPH, graph ) + .getResultList(); + + assertEquals( 2, students.size() ); + }); + } + @Before public void createData() { EntityManager entityManager = getOrCreateEntityManager(); @@ -348,6 +392,6 @@ public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCas @Override protected Class[] getAnnotatedClasses() { - return new Class[] { Company.class, Employee.class, Manager.class, Location.class }; + return new Class[] { Company.class, Employee.class, Manager.class, Location.class, Course.class, Student.class }; } }