/*
* 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.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;
import javax.persistence.Subgraph;
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;
import static org.junit.Assert.assertTrue;
/**
* @author Brett Meyer
*/
public class QueryHintEntityGraphTest extends BaseEntityManagerFunctionalTestCase {
// TODO: Currently, "loadgraph" and "fetchgraph" operate identically in JPQL. The spec states that "fetchgraph"
// shall use LAZY for non-specified attributes, ignoring their metadata. Changes to ToOne select vs. join,
// allowing queries to force laziness, etc. will require changes here and impl logic.
@Test
public void testLoadGraph() {
EntityManager entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
entityGraph.addAttributeNodes( "location" );
entityGraph.addAttributeNodes( "markets" );
Query query = entityManager.createQuery( "from " + Company.class.getName() );
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
Company company = (Company) query.getSingleResult();
entityManager.getTransaction().commit();
entityManager.close();
assertFalse( Hibernate.isInitialized( company.employees ) );
assertTrue( Hibernate.isInitialized( company.location ) );
assertTrue( Hibernate.isInitialized( company.markets ) );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( company.phoneNumbers ) );
entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
Subgraph<Employee> subgraph = entityGraph.addSubgraph( "employees" );
subgraph.addAttributeNodes( "managers" );
subgraph.addAttributeNodes( "friends" );
Subgraph<Manager> subSubgraph = subgraph.addSubgraph( "managers", Manager.class );
subSubgraph.addAttributeNodes( "managers" );
subSubgraph.addAttributeNodes( "friends" );
query = entityManager.createQuery( "from " + Company.class.getName() );
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
company = (Company) query.getSingleResult();
entityManager.getTransaction().commit();
entityManager.close();
assertTrue( Hibernate.isInitialized( company.employees ) );
assertTrue( Hibernate.isInitialized( company.location ) );
assertEquals( 12345, company.location.zip );
assertTrue( Hibernate.isInitialized( company.markets ) );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( company.phoneNumbers ) );
boolean foundManager = false;
Iterator<Employee> employeeItr = company.employees.iterator();
while (employeeItr.hasNext()) {
Employee employee = employeeItr.next();
assertTrue( Hibernate.isInitialized( employee.managers ) );
assertTrue( Hibernate.isInitialized( employee.friends ) );
// test 1 more level
Iterator<Manager> managerItr = employee.managers.iterator();
while (managerItr.hasNext()) {
foundManager = true;
Manager manager = managerItr.next();
assertTrue( Hibernate.isInitialized( manager.managers ) );
assertTrue( Hibernate.isInitialized( manager.friends ) );
}
}
assertTrue(foundManager);
}
@Test
@TestForIssue( jiraKey = "HHH-9457")
public void testLoadGraphOrderByWithImplicitJoin() {
EntityManager entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
// create a new Company at a different location in a different zip code
Location location = new Location();
location.address = "123 somewhere";
location.zip = 11234;
entityManager.persist( location );
Company companyNew = new Company();
companyNew.location = location;
entityManager.persist( companyNew );
entityManager.getTransaction().commit();
entityManager.close();
entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
//entityGraph.addAttributeNodes( "location" );
entityGraph.addAttributeNodes( "markets" );
Query query = entityManager.createQuery( "from " + Company.class.getName() + " c order by c.location.zip, c.id" );
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
List results = query.getResultList();
// - 1st will be the Company with location.zip == 11234 with an empty markets collection
// - 2nd should be the Company with location.zip == 12345
assertEquals( 2, results.size() );
Company companyResult = (Company) results.get( 0 );
assertFalse( Hibernate.isInitialized( companyResult.employees ) );
assertFalse( Hibernate.isInitialized( companyResult.location ) );
// initialize and check zip
// TODO: must have getters to access lazy entity after being initialized (why?)
//assertEquals( 11234, companyResult.location.zip );
assertEquals( 11234, companyResult.getLocation().getZip() );
assertTrue( Hibernate.isInitialized( companyResult.markets ) );
assertEquals( 0, companyResult.markets.size() );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( companyResult.phoneNumbers ) );
assertEquals( 0, companyResult.phoneNumbers.size() );
companyResult = (Company) results.get( 1 );
assertFalse( Hibernate.isInitialized( companyResult.employees ) );
assertFalse( Hibernate.isInitialized( companyResult.location ) );
// initialize and check zip
// TODO: must have getters to access lazy entity after being initialized (why?)
//assertEquals( 12345, companyResult.location.zip );
assertEquals( 12345, companyResult.getLocation().getZip() );
assertTrue( Hibernate.isInitialized( companyResult.markets ) );
assertEquals( 2, companyResult.markets.size() );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( companyResult.phoneNumbers ) );
assertEquals( 2, companyResult.phoneNumbers.size() );
entityManager.getTransaction().commit();
entityManager.close();
}
@Test
@TestForIssue( jiraKey = "HHH-9448")
public void testLoadGraphWithRestriction() {
EntityManager entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
entityGraph.addAttributeNodes( "location" );
entityGraph.addAttributeNodes( "markets" );
Query query = entityManager.createQuery( "from " + Company.class.getName() + " where location.zip = :zip")
.setParameter( "zip", 12345 );
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
Company company = (Company) query.getSingleResult();
entityManager.getTransaction().commit();
entityManager.close();
assertFalse( Hibernate.isInitialized( company.employees ) );
assertTrue( Hibernate.isInitialized( company.location ) );
assertTrue( Hibernate.isInitialized( company.markets ) );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( company.phoneNumbers ) );
entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
Subgraph<Employee> subgraph = entityGraph.addSubgraph( "employees" );
subgraph.addAttributeNodes( "managers" );
subgraph.addAttributeNodes( "friends" );
Subgraph<Manager> subSubgraph = subgraph.addSubgraph( "managers", Manager.class );
subSubgraph.addAttributeNodes( "managers" );
subSubgraph.addAttributeNodes( "friends" );
query = entityManager.createQuery( "from " + Company.class.getName() + " where location.zip = :zip" )
.setParameter( "zip", 12345 );
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
company = (Company) query.getSingleResult();
entityManager.getTransaction().commit();
entityManager.close();
assertTrue( Hibernate.isInitialized( company.employees ) );
assertTrue( Hibernate.isInitialized( company.location ) );
assertTrue( Hibernate.isInitialized( company.markets ) );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( company.phoneNumbers ) );
boolean foundManager = false;
Iterator<Employee> employeeItr = company.employees.iterator();
while (employeeItr.hasNext()) {
Employee employee = employeeItr.next();
assertTrue( Hibernate.isInitialized( employee.managers ) );
assertTrue( Hibernate.isInitialized( employee.friends ) );
// test 1 more level
Iterator<Manager> managerItr = employee.managers.iterator();
while (managerItr.hasNext()) {
foundManager = true;
Manager manager = managerItr.next();
assertTrue( Hibernate.isInitialized( manager.managers ) );
assertTrue( Hibernate.isInitialized( manager.friends ) );
}
}
assertTrue(foundManager);
}
@Test
public void testEntityGraphWithExplicitFetch() {
EntityManager entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
entityGraph.addAttributeNodes( "location" );
entityGraph.addAttributeNodes( "markets" );
entityGraph.addAttributeNodes( "employees" );
// Ensure the EntityGraph and explicit fetches do not conflict.
Query query = entityManager.createQuery( "from " + Company.class.getName()
+ " as c left join fetch c.location left join fetch c.employees" );
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
Company company = (Company) query.getSingleResult();
entityManager.getTransaction().commit();
entityManager.close();
assertTrue( Hibernate.isInitialized( company.employees ) );
assertTrue( Hibernate.isInitialized( company.location ) );
assertTrue( Hibernate.isInitialized( company.markets ) );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( company.phoneNumbers ) );
}
@Test
@TestForIssue( jiraKey = "HHH-9448")
public void testEntityGraphWithExplicitFetchAndRestriction() {
EntityManager entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
EntityGraph<Company> entityGraph = entityManager.createEntityGraph( Company.class );
entityGraph.addAttributeNodes( "location" );
entityGraph.addAttributeNodes( "markets" );
entityGraph.addAttributeNodes( "employees" );
// Ensure the EntityGraph and explicit fetches do not conflict.
Query query = entityManager.createQuery( "from " + Company.class.getName()
+ " as c left join fetch c.location left join fetch c.employees where c.location.zip = :zip")
.setParameter("zip", 12345);
query.setHint( QueryHints.HINT_LOADGRAPH, entityGraph );
Company company = (Company) query.getSingleResult();
entityManager.getTransaction().commit();
entityManager.close();
assertTrue( Hibernate.isInitialized( company.employees ) );
assertTrue( Hibernate.isInitialized( company.location ) );
assertTrue( Hibernate.isInitialized( company.markets ) );
// With "loadgraph", non-specified attributes use the fetch modes defined in the mappings. So, here,
// @ElementCollection(fetch = FetchType.EAGER) should cause the follow-on selects to happen.
assertTrue( Hibernate.isInitialized( company.phoneNumbers ) );
}
@Test
@TestForIssue(jiraKey = "HHH-9374")
public void testEntityGraphWithCollectionSubquery(){
EntityManager entityManager = getOrCreateEntityManager();
entityManager.getTransaction().begin();
EntityGraph<Company> entityGraph = entityManager.createEntityGraph(Company.class);
entityGraph.addAttributeNodes("location");
Query query = entityManager.createQuery("select c from " + Company.class.getName() + " c where c.employees IS EMPTY");
query.setHint(QueryHints.HINT_LOADGRAPH, entityGraph);
query.getResultList();
entityManager.getTransaction().commit();
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();
entityManager.getTransaction().begin();
Manager manager1 = new Manager();
entityManager.persist( manager1 );
Manager manager2 = new Manager();
manager2.managers.add( manager1 );
entityManager.persist( manager2 );
Employee employee = new Employee();
employee.managers.add( manager1 );
entityManager.persist( employee );
Location location = new Location();
location.address = "123 somewhere";
location.zip = 12345;
entityManager.persist( location );
Company company = new Company();
company.employees.add( employee );
company.employees.add( manager1 );
company.employees.add( manager2 );
company.location = location;
company.markets.add( Market.SERVICES );
company.markets.add( Market.TECHNOLOGY );
company.phoneNumbers.add( "012-345-6789" );
company.phoneNumbers.add( "987-654-3210" );
entityManager.persist( company );
entityManager.getTransaction().commit();
entityManager.close();
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Company.class, Employee.class, Manager.class, Location.class, Course.class, Student.class };
}
}