HHH-11569 - Return only distinct elements when query is hinted with EntityGraph
This commit is contained in:
parent
61612ca540
commit
a23a9a81e3
|
@ -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() ) {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
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<Student> 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<Student> getStudents() {
|
||||
return students;
|
||||
}
|
||||
|
||||
public void setStudents(List<Student> 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 + "]";
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
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<Course> courses;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Set<Course> getCourses() {
|
||||
return courses;
|
||||
}
|
||||
|
||||
public void setCourses(Set<Course> 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 + "]";
|
||||
}
|
||||
}
|
|
@ -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<Course> std1Courses = new HashSet<Course>();
|
||||
std1Courses.add( course1 );
|
||||
std1Courses.add( course2 );
|
||||
student1.setCourses( std1Courses );
|
||||
|
||||
Set<Course> std2Courses = new HashSet<Course>();
|
||||
std2Courses.add( course2 );
|
||||
student2.setCourses( std2Courses );
|
||||
|
||||
entityManager.persist( student1 );
|
||||
entityManager.persist( student2 );
|
||||
|
||||
});
|
||||
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
EntityGraph<?> graph = entityManager.getEntityGraph( "Student.Full" );
|
||||
|
||||
List<Student> 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 };
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue