HHH-11569 - Return only distinct elements when query is hinted with EntityGraph

This commit is contained in:
Harsh Panchal 2017-03-14 09:38:00 +05:30 committed by Vlad Mihalcea
parent 61612ca540
commit a23a9a81e3
4 changed files with 230 additions and 8 deletions

View File

@ -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() ) {

View File

@ -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 + "]";
}
}

View File

@ -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 + "]";
}
}

View File

@ -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 };
}
}