blob: 56a514f9128303bea3f9dddb31f613a9b36b1581 [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// 05/19/2010-2.1 ailitchev - Bug 244124 - Add Nested FetchGroup
// 09/21/2010-2.2 Frank Schwarz and ailitchev - Bug 325684 - QueryHints.BATCH combined with QueryHints.FETCH_GROUP_LOAD will cause NPE
package org.eclipse.persistence.testing.tests.jpa.fetchgroups;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import junit.framework.TestSuite;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.internal.helper.IdentityHashSet;
import org.eclipse.persistence.internal.jpa.EntityManagerImpl;
import org.eclipse.persistence.internal.queries.EntityFetchGroup;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.jpa.JpaHelper;
import org.eclipse.persistence.queries.AttributeGroup;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.FetchGroupTracker;
import org.eclipse.persistence.queries.LoadGroup;
import org.eclipse.persistence.testing.models.jpa.advanced.Address;
import org.eclipse.persistence.testing.models.jpa.advanced.Department;
import org.eclipse.persistence.testing.models.jpa.advanced.Employee;
import org.eclipse.persistence.testing.models.jpa.advanced.PhoneNumber;
import org.eclipse.persistence.testing.models.jpa.advanced.Project;
import org.eclipse.persistence.testing.models.jpa.advanced.Employee.Gender;
import org.eclipse.persistence.testing.models.jpa.advanced.compositepk.Competency;
import org.junit.Test;
/**
* @author dclarke
* @since EclipseLink 2.1
*/
public class NestedFetchGroupTests extends BaseFetchGroupTests {
public NestedFetchGroupTests() {
super();
}
public NestedFetchGroupTests(String name) {
super(name);
}
public static junit.framework.Test suite() {
TestSuite suite = new TestSuite();
suite.setName("NestedFetchGroupTests");
suite.addTest(new NestedFetchGroupTests("testSetup"));
suite.addTest(new NestedFetchGroupTests("dynamicFetchGroup_EmployeeAddress"));
suite.addTest(new NestedFetchGroupTests("dynamicFetchGroup_Employee_NullAddress"));
suite.addTest(new NestedFetchGroupTests("dynamicFetchGroup_EmployeeAddressNullPhone"));
suite.addTest(new NestedFetchGroupTests("dynamicFetchGroup_EmployeeAddressEmptyPhone"));
suite.addTest(new NestedFetchGroupTests("dynamicFetchGroup_EmployeeAddressEmptyPhoneLoad"));
suite.addTest(new NestedFetchGroupTests("dynamicHierarchicalFetchGroup"));
suite.addTest(new NestedFetchGroupTests("dynamicFetchGroup_ElementCollection"));
//**temp suite.addTest(new NestedFetchGroupTests("dynamicHierarchicalFetchGroup_JOIN_FETCH"));
suite.addTest(new NestedFetchGroupTests("dynamicHierarchicalFetchGroup_JOIN_FETCH_Copy"));
//**temp suite.addTest(new NestedFetchGroupTests("managerDoubleNestedFetchGroupWithJoinFetch"));
suite.addTest(new NestedFetchGroupTests("managerTripleNestedFetchGroupWithJoinFetch"));
suite.addTest(new NestedFetchGroupTests("allNestedFetchGroupWithJoinFetch"));
suite.addTest(new NestedFetchGroupTests("joinFetchDefaultFetchGroup"));
suite.addTest(new NestedFetchGroupTests("joinFetchOutsideOfFetchGroup"));
suite.addTest(new NestedFetchGroupTests("simpleNestedFetchGroupWithBatch"));
suite.addTest(new NestedFetchGroupTests("simpleLoadGroup"));
suite.addTest(new NestedFetchGroupTests("simpleFetchGroupLoadWithBatch"));
return suite;
}
@Override
public void setUp() {
super.setUp();
defaultPhoneFG = new FetchGroup();
defaultPhoneFG.addAttribute("number");
phoneDescriptor.getFetchGroupManager().setDefaultFetchGroup(defaultPhoneFG);
reprepareReadQueries(phoneDescriptor);
reprepareReadQueries(employeeDescriptor);
// We'll put a default FetchGroup on Phone
assertNotNull(phoneDescriptor.getDefaultFetchGroup());
assertNotNull(phoneDescriptor.getDescriptorQueryManager().getReadObjectQuery().getExecutionFetchGroup());
assertTrue(phoneDescriptor.getFetchGroupManager().getFetchGroups().isEmpty());
}
@Test
public void dynamicFetchGroup_EmployeeAddress() throws Exception {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.gender = :GENDER");
query.setParameter("GENDER", Gender.Male);
// Define the fields to be fetched on Employee
FetchGroup fg = new FetchGroup();
fg.addAttribute("id");
fg.addAttribute("version");
fg.addAttribute("firstName");
fg.addAttribute("lastName");
fg.addAttribute("address.city");
fg.addAttribute("address.postalCode");
// Configure the dynamic FetchGroup
query.setHint(QueryHints.FETCH_GROUP, fg);
List<Employee> emps = query.getResultList();
assertNotNull(emps);
for (Employee emp : emps) {
FetchGroupTracker tracker = (FetchGroupTracker) emp;
assertNotNull(tracker._persistence_getFetchGroup());
// Verify specified fields plus mandatory ones are loaded
assertTrue(tracker._persistence_isAttributeFetched("firstName"));
assertTrue(tracker._persistence_isAttributeFetched("lastName"));
assertTrue(tracker._persistence_isAttributeFetched("address"));
FetchGroupTracker addrTracker = (FetchGroupTracker) emp.getAddress();
assertTrue(addrTracker._persistence_isAttributeFetched("city"));
assertTrue(addrTracker._persistence_isAttributeFetched("postalCode"));
assertFalse(addrTracker._persistence_isAttributeFetched("street"));
// Verify the other fields are not loaded
assertFalse(tracker._persistence_isAttributeFetched("salary"));
assertFalse(tracker._persistence_isAttributeFetched("startTime"));
assertFalse(tracker._persistence_isAttributeFetched("endTime"));
// Force the loading of lazy fields and verify
emp.getSalary();
assertTrue(tracker._persistence_isAttributeFetched("firstName"));
assertTrue(tracker._persistence_isAttributeFetched("lastName"));
assertTrue(tracker._persistence_isAttributeFetched("address"));
assertTrue(tracker._persistence_isAttributeFetched("salary"));
assertTrue(tracker._persistence_isAttributeFetched("startTime"));
assertTrue(tracker._persistence_isAttributeFetched("endTime"));
// Now we'll check the address uses the provided dynamic fetch-group
addrTracker = (FetchGroupTracker) emp.getAddress();
assertNotNull("Address does not have a FetchGroup", addrTracker._persistence_getFetchGroup());
assertTrue(addrTracker._persistence_isAttributeFetched("city"));
assertTrue(addrTracker._persistence_isAttributeFetched("postalCode"));
assertFalse(addrTracker._persistence_isAttributeFetched("street"));
assertFalse(addrTracker._persistence_isAttributeFetched("country"));
// Now we'll check the phoneNumbers use of the default fetch group
for (PhoneNumber phone : emp.getPhoneNumbers()) {
FetchGroupTracker phoneTracker = (FetchGroupTracker) phone;
assertNotNull("PhoneNumber does not have a FetchGroup", phoneTracker._persistence_getFetchGroup());
assertTrue(phoneTracker._persistence_isAttributeFetched("number"));
assertFalse(phoneTracker._persistence_isAttributeFetched("areaCode"));
}
}
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void dynamicFetchGroup_Employee_NullAddress() throws Exception {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.gender = :GENDER");
query.setParameter("GENDER", Gender.Male);
// Define the fields to be fetched on Employee
FetchGroup empGroup = new FetchGroup();
empGroup.addAttribute("firstName");
empGroup.addAttribute("lastName");
empGroup.addAttribute("address");
// Define the fields to be fetched on Address
FetchGroup addressGroup = new FetchGroup();
addressGroup.addAttribute("city");
addressGroup.addAttribute("postalCode");
empGroup.addAttribute("address");
// Configure the dynamic FetchGroup
query.setHint(QueryHints.FETCH_GROUP, empGroup);
List<Employee> emps = query.getResultList();
assertNotNull(emps);
for (Employee emp : emps) {
FetchGroupTracker tracker = (FetchGroupTracker) emp;
assertNotNull(tracker._persistence_getFetchGroup());
// Verify specified fields plus mandatory ones are loaded
assertTrue(tracker._persistence_isAttributeFetched("id"));
assertTrue(tracker._persistence_isAttributeFetched("firstName"));
assertTrue(tracker._persistence_isAttributeFetched("lastName"));
assertTrue(tracker._persistence_isAttributeFetched("version"));
// Verify the other fields are not loaded
assertFalse(tracker._persistence_isAttributeFetched("salary"));
assertFalse(tracker._persistence_isAttributeFetched("startTime"));
assertFalse(tracker._persistence_isAttributeFetched("endTime"));
// Force the loading of lazy fields and verify
emp.getSalary();
assertTrue(tracker._persistence_isAttributeFetched("salary"));
assertTrue(tracker._persistence_isAttributeFetched("startTime"));
assertTrue(tracker._persistence_isAttributeFetched("endTime"));
// Now we'll check the address uses the provided dynamic fetch-group
FetchGroupTracker addrTracker = (FetchGroupTracker) emp.getAddress();
assertNull("Address has an unexpected FetchGroup", addrTracker._persistence_getFetchGroup());
// Now we'll check the phoneNumbers use of the default fetch group
for (PhoneNumber phone : emp.getPhoneNumbers()) {
FetchGroupTracker phoneTracker = (FetchGroupTracker) phone;
assertNotNull("PhoneNumber does not have a FetchGroup", phoneTracker._persistence_getFetchGroup());
assertTrue(phoneTracker._persistence_isAttributeFetched("number"));
assertFalse(phoneTracker._persistence_isAttributeFetched("areaCode"));
}
}
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void dynamicFetchGroup_EmployeeAddressNullPhone() throws Exception {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.gender = :GENDER");
query.setParameter("GENDER", Gender.Male);
// Define the fields to be fetched on Employee
FetchGroup empGroup = new FetchGroup();
empGroup.addAttribute("firstName");
empGroup.addAttribute("lastName");
empGroup.addAttribute("address");
empGroup.addAttribute("address.city");
empGroup.addAttribute("address.postalCode");
//empGroup.addAttribute("phoneNumbers").setUseDefaultFetchGroup(false);
FetchGroup fullPhone = this.phoneDescriptor.getFetchGroupManager().createFullFetchGroup();
// to preclude Employee from being loaded by phoneNumber.owner add it to the fetch group
fullPhone.addAttribute("owner.id");
empGroup.addAttribute("phoneNumbers", fullPhone);
// Configure the dynamic FetchGroup
query.setHint(QueryHints.FETCH_GROUP, empGroup);
List<Employee> emps = query.getResultList();
assertNotNull(emps);
for (Employee emp : emps) {
FetchGroupTracker tracker = (FetchGroupTracker) emp;
assertNotNull(tracker._persistence_getFetchGroup());
// Verify specified fields plus mandatory ones are loaded
assertTrue(tracker._persistence_isAttributeFetched("id"));
assertTrue(tracker._persistence_isAttributeFetched("firstName"));
assertTrue(tracker._persistence_isAttributeFetched("lastName"));
assertTrue(tracker._persistence_isAttributeFetched("version"));
// Verify the other fields are not loaded
assertFalse(tracker._persistence_isAttributeFetched("salary"));
assertFalse(tracker._persistence_isAttributeFetched("startTime"));
assertFalse(tracker._persistence_isAttributeFetched("endTime"));
// Force the loading of lazy fields and verify
emp.getSalary();
assertTrue(tracker._persistence_isAttributeFetched("salary"));
assertTrue(tracker._persistence_isAttributeFetched("startTime"));
assertTrue(tracker._persistence_isAttributeFetched("endTime"));
// Now we'll check the address uses the provided dynamic fetch-group
FetchGroupTracker addrTracker = (FetchGroupTracker) emp.getAddress();
assertNotNull("Address does not have a FetchGroup", addrTracker._persistence_getFetchGroup());
assertTrue(addrTracker._persistence_isAttributeFetched("city"));
assertTrue(addrTracker._persistence_isAttributeFetched("postalCode"));
assertFalse(addrTracker._persistence_isAttributeFetched("street"));
assertFalse(addrTracker._persistence_isAttributeFetched("country"));
// Now we'll check the phoneNumbers use of the default fetch group
for (PhoneNumber phone : emp.getPhoneNumbers()) {
FetchGroupTracker phoneTracker = (FetchGroupTracker) phone;
assertNull("PhoneNumber has a FetchGroup", phoneTracker._persistence_getFetchGroup());
}
}
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void dynamicFetchGroup_ElementCollection(){
EntityManager em = createEntityManager();
AttributeGroup compt = new AttributeGroup(null, Competency.class, true);
compt.addAttribute("description");
AttributeGroup fg = new AttributeGroup(null, org.eclipse.persistence.testing.models.jpa.advanced.compositepk.Department.class, true);
fg.addAttribute("competencies", compt);
clearCache();
Collection<org.eclipse.persistence.testing.models.jpa.advanced.compositepk.Department> results = em.createQuery("select d from Department d").setHint(QueryHints.FETCH_GROUP, fg.toFetchGroup()).getResultList();
for (org.eclipse.persistence.testing.models.jpa.advanced.compositepk.Department dept : results){
assertFalse("Collection fetched: scientists, fg ignored", ((FetchGroupTracker)dept)._persistence_isAttributeFetched("scientists"));
assertFalse("Collection fetched: offices, fg ignored", ((FetchGroupTracker)dept)._persistence_isAttributeFetched("offices"));
assertTrue("Collection not fetched: competencies, fg ignored", ((FetchGroupTracker)dept)._persistence_isAttributeFetched("competencies"));
for (Competency embeded: dept.getCompetencies()){
assertTrue("Element attribute not loaded: description, fg ignored", ((FetchGroupTracker)embeded)._persistence_isAttributeFetched("description"));
}
dept.getScientists().size();
assertTrue("Collection not fetched: scientists, fg ignored", ((FetchGroupTracker)dept)._persistence_isAttributeFetched("scientists"));
assertTrue("Collection not fetched: offices, fg ignored", ((FetchGroupTracker)dept)._persistence_isAttributeFetched("offices"));
assertTrue("Collection not fetched: competencies, fg ignored", ((FetchGroupTracker)dept)._persistence_isAttributeFetched("competencies"));
for (Competency embeded: dept.getCompetencies()){
embeded.getRating();
assertTrue("Element attribute not loaded: description, fg ignored", ((FetchGroupTracker)embeded)._persistence_isAttributeFetched("description"));
}
}
}
@Test
public void dynamicFetchGroup_EmployeeAddressEmptyPhone() {
}
public void dynamicFetchGroup_EmployeeAddressEmptyPhoneLoad() {
}
void internal_dynamicFetchGroup_EmployeeAddressEmptyPhone(boolean shouldLoad) {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.gender = :GENDER");
query.setParameter("GENDER", Gender.Male);
// Define the fields to be fetched on Employee
FetchGroup fg = new FetchGroup();
fg.addAttribute("firstName");
fg.addAttribute("lastName");
fg.addAttribute("address.city");
fg.addAttribute("address.postalCode");
// to preclude Employee from being loaded by phoneNumber.owner add it to the fetch group
FetchGroup ownerId = new FetchGroup();
ownerId.addAttribute("owner.id");
fg.addAttribute("phoneNumbers", ownerId);
if(shouldLoad) {
fg.setShouldLoad(true);
}
// Configure the dynamic FetchGroup
query.setHint(QueryHints.FETCH_GROUP, fg);
List<Employee> emps = query.getResultList();
assertNotNull(emps);
assertEquals(1 + (shouldLoad ? 0 : (emps.size() * 2)), getQuerySQLTracker(em).getTotalSQLSELECTCalls());
for (Employee emp : emps) {
FetchGroupTracker tracker = (FetchGroupTracker) emp;
assertNotNull(tracker._persistence_getFetchGroup());
// Verify specified fields plus mandatory ones are loaded
assertTrue(tracker._persistence_isAttributeFetched("id"));
assertTrue(tracker._persistence_isAttributeFetched("firstName"));
assertTrue(tracker._persistence_isAttributeFetched("lastName"));
assertTrue(tracker._persistence_isAttributeFetched("version"));
// Verify the other fields are not loaded
assertFalse(tracker._persistence_isAttributeFetched("salary"));
assertFalse(tracker._persistence_isAttributeFetched("startTime"));
assertFalse(tracker._persistence_isAttributeFetched("endTime"));
// Force the loading of lazy fields and verify
emp.getSalary();
assertTrue(tracker._persistence_isAttributeFetched("salary"));
assertTrue(tracker._persistence_isAttributeFetched("startTime"));
assertTrue(tracker._persistence_isAttributeFetched("endTime"));
// Now we'll check the address uses the provided dynamic fetch-group
FetchGroupTracker addrTracker = (FetchGroupTracker) emp.getAddress();
assertNotNull("Address does not have a FetchGroup", addrTracker._persistence_getFetchGroup());
assertTrue(addrTracker._persistence_isAttributeFetched("city"));
assertTrue(addrTracker._persistence_isAttributeFetched("postalCode"));
assertFalse(addrTracker._persistence_isAttributeFetched("street"));
assertFalse(addrTracker._persistence_isAttributeFetched("country"));
// Now we'll check the phoneNumbers use of the default fetch group
for (PhoneNumber phone : emp.getPhoneNumbers()) {
FetchGroupTracker phoneTracker = (FetchGroupTracker) phone;
assertNotNull("PhoneNumber does not have a FetchGroup", phoneTracker._persistence_getFetchGroup());
assertFalse(phoneTracker._persistence_isAttributeFetched("number"));
assertFalse(phoneTracker._persistence_isAttributeFetched("areaCode"));
phone.getNumber();
assertTrue(phoneTracker._persistence_isAttributeFetched("number"));
assertTrue(phoneTracker._persistence_isAttributeFetched("areaCode"));
}
}
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void dynamicHierarchicalFetchGroup() throws Exception {
EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.lastName LIKE :LNAME AND e.manager.lastName <> e.lastName");
query.setParameter("LNAME", "%");
// Define the fields to be fetched on Employee
FetchGroup fg = new FetchGroup();
fg.addAttribute("firstName");
fg.addAttribute("lastName");
fg.addAttribute("salary");
fg.addAttribute("gender");
fg.addAttribute("manager.firstName");
fg.addAttribute("manager.lastName");
fg.addAttribute("manager.salary");
fg.addAttribute("manager.gender");
fg.addAttribute("manager.manager.firstName");
fg.addAttribute("manager.manager.lastName");
fg.addAttribute("manager.manager.salary");
fg.addAttribute("manager.manager.gender");
query.setHint(QueryHints.FETCH_GROUP, fg);
List<Employee> emps = query.getResultList();
int numSelect = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
for (Employee emp : emps) {
assertFetched(emp, fg);
}
assertEquals(numSelect, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
}
@Test
public void dynamicHierarchicalFetchGroup_JOIN_FETCH() throws Exception {
internalDynamicHierarchicalFetchGroup_JOIN_FETCH(false);
}
@Test
public void dynamicHierarchicalFetchGroup_JOIN_FETCH_Copy() throws Exception {
internalDynamicHierarchicalFetchGroup_JOIN_FETCH(true);
}
void internalDynamicHierarchicalFetchGroup_JOIN_FETCH(boolean useCopy) throws Exception {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e JOIN FETCH e.manager WHERE e.lastName LIKE :LNAME AND e.manager.lastName <> e.lastName");
query.setParameter("LNAME", "%");
// Define the fields to be fetched on Employee
FetchGroup fg = new FetchGroup();
fg.addAttribute("firstName");
fg.addAttribute("lastName");
fg.addAttribute("manager.firstName");
fg.addAttribute("manager.salary");
fg.addAttribute("manager.manager");
query.setHint(QueryHints.FETCH_GROUP, fg);
// applied to the selected Employee who is not a manager of some other selected Employee
FetchGroup employeeFG = new EntityFetchGroup(new String[]{"id", "version", "firstName", "lastName", "manager"});
// applied to the manager of a selected Employee who is not selected as an Employee
FetchGroup managerFG = new EntityFetchGroup(new String[]{"id", "version", "firstName", "salary", "manager"});
// applied to the object which is both selected as an Employee and the manager of another selected Employee
FetchGroup employeeManagerFG = employeeDescriptor.getFetchGroupManager().flatUnionFetchGroups(employeeFG, managerFG, false);
// used in useCopy case only
FetchGroup employeeManagerManagerFG = null;
if(useCopy) {
employeeManagerManagerFG = employeeDescriptor.getFetchGroupManager().flatUnionFetchGroups(new EntityFetchGroup("manager"), employeeDescriptor.getFetchGroupManager().getNonReferenceEntityFetchGroup(), false);
}
/*
* These are the first names of Employees involved; --> means "managed by".
* All the employees here are returned by the query except Jill and Sarah-loo (they got no manager).
*
* Sarah ------>Bob -------> John ----> Jim-bob ---> Jill ---> null
* Charles -----^ Marius ----^
*
* Nancy ------> Sarah-loo ---> null
*
* Sarah, Charles, Nancy should have employeeFG;
* Sarah-loo - managerFG;
* Bob, Marius - employeeManagerFG;
* John, Jim-bob should have a union of three fetch groups: {firstName,lastName,manager}, {firstName,salary,manager}, {manager}
* Jill should have a union of two groups: {firstName,salary,manager}, {manager}
* The result for all three of them is the same:
* in read case (useCopy == false) it should be null (no fetch group), because defaultFetchGroup is null;
* in copy case (useCopy == true) it should be a union of "manager" and all non relational attributes (NonReferenceEntityFetchGroup).
* That's how leaf reference attribute is treated:
* default fetch group for read;
* NonReferenceEntityFetchGroup (see FetchGroupManager) for copy.
* In this test defaultFetchGroup (null) / NonReferenceEntityFetchGroup comes from {manager},
* in useCopy == true case additional manager comes from another fetch group (they all contain manager).
*/
List<Employee> emps = query.getResultList();
if(useCopy) {
/*for(Employee emp : emps) {
int idHashCode = System.identityHashCode(emp);
System.out.println(emp.getFirstName() + '\t' + idHashCode);
}*/
emps = (List)JpaHelper.getEntityManager(em).copy(emps, fg);
}
// Sets of managed Employees keyed by their manager
Map<Employee, Set<Employee>> managedEmployeesByManager = new IdentityHashMap();
for (Employee emp : emps) {
Employee manager = emp.getManager();
Set<Employee> managedEmployees = managedEmployeesByManager.get(manager);
if(managedEmployees == null) {
managedEmployees = new IdentityHashSet();
managedEmployeesByManager.put(manager, managedEmployees);
}
managedEmployees.add(emp);
}
for (Employee emp : emps) {
Set<Employee> managedEmployees = managedEmployeesByManager.get(emp);
Employee manager = emp.getManager();
if(managedEmployees == null) {
// employee is NOT a manager of any of the selected employees:
assertFetched(emp, employeeFG);
Set<Employee> managedByManagerEmployees = managedEmployeesByManager.get(manager);
// indicates whether one of manager's managed employees is a manager itself
boolean isManagersManager = false;
for(Employee managedEmp : managedByManagerEmployees) {
if(managedEmployeesByManager.containsKey(managedEmp)) {
isManagersManager = true;
break;
}
}
if(isManagersManager) {
if(useCopy) {
// for at least one of the selected employees manager is manager's manager:
// someSelectedEmp.getManager().getManager() == manager
// That means for someSelectedEmp emp is defined by {manager.manager} FetchGroup's item,
// which means NonReferenceEntityFetchGroup (only non-reference attributes + pk)
// for another employee it's just a manager - which means it should include "manager":
// employeeManagerManagerFG is the union of these two EntityFetchGroups.
assertFetched(manager, employeeManagerManagerFG);
} else {
// for at least one of the selected employees manager is manager's manager:
// someSelectedEmp.getManager().getManager() == manager
// That means for someSelectedEmp emp is defined by {manager.manager} FetchGroup's item,
// which means no fetch group should be used.
assertNoFetchGroup(manager);
}
} else {
// it's not manager's manager
if(emps.contains(manager)) {
// it's a manager of one of the selected Employees, and selected itself.
assertFetched(manager, employeeManagerFG);
} else {
// it's a manager of one of the selected Employees, but not selected itself.
assertFetched(manager, managerFG);
}
}
} else {
// employee is a manager of at least one of the selected employees
// indicates whether one of emp's managed employees is a manager itself
boolean isManagersManager = false;
for(Employee managedEmp : managedEmployees) {
if(managedEmployeesByManager.containsKey(managedEmp)) {
isManagersManager = true;
break;
}
}
if(isManagersManager) {
if(useCopy) {
// for at least one of the selected employees manager is manager's manager:
// someSelectedEmp.getManager().getManager() == manager
// That means for someSelectedEmp emp is defined by {manager.manager} FetchGroup's item,
// which means NonReferenceEntityFetchGroup (only non-reference attributes + pk)
// for another employee it's just a manager - which means it should include "manager":
// employeeManagerManagerFG is the union of these two EntityFetchGroups.
assertFetched(emp, employeeManagerManagerFG);
} else {
// for at least one of the selected employees emp is manager's manager:
// someSelectedEmp.getManager().getManager() == emp
// That means for someSelectedEmp emp is defined by {manager.manager} FetchGroup's item,
// which means no fetch group should be used.
assertNoFetchGroup(emp);
}
} else {
// it's selected employee, manager of some selected employee, but not manager's manager
assertFetched(emp, employeeManagerFG);
}
if(useCopy) {
// for at least one of the selected employees manager is manager's manager:
// someSelectedEmp.getManager().getManager() == manager
// That means for someSelectedEmp emp is defined by {manager.manager} FetchGroup's item,
// which means NonReferenceEntityFetchGroup (only non-reference attributes + pk)
// for another employee it's just a manager - which means it should include "manager":
// employeeManagerManagerFG is the union of these two EntityFetchGroups.
assertFetched(manager, employeeManagerManagerFG);
} else {
// for at least one of the selected employees manager is manager's manager:
// someSelectedEmp.getManager().getManager() == manager
// That means for someSelectedEmp emp.getManager() is defined by {manager.manager} FetchGroup's item,
// which means no fetch group should be used.
assertNoFetchGroup(manager);
}
}
}
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void managerDoubleNestedFetchGroupWithJoinFetch() {
managerNestedFetchGroupWithJoinFetch(true);
}
@Test
public void managerTripleNestedFetchGroupWithJoinFetch() {
managerNestedFetchGroupWithJoinFetch(false);
}
void managerNestedFetchGroupWithJoinFetch(boolean isDouble) {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.manager.manager IS NOT NULL");
FetchGroup managerFG = new FetchGroup();
if(isDouble) {
// Double
managerFG.addAttribute("manager.manager");
} else {
// Triple
managerFG.addAttribute("manager.manager.manager");
}
query.setHint(QueryHints.FETCH_GROUP, managerFG);
query.setHint(QueryHints.LEFT_FETCH, "e.manager");
assertNotNull(getFetchGroup(query));
assertSame(managerFG, getFetchGroup(query));
List<Employee> employees = query.getResultList();
int nSql;
if(isDouble) {
// In this case the number of generated sqls is unpredictable.
// Additional sql generated for every object that
// has been first fetched as manager.manager
// and then is selected as an employee - getting its manger
// performed without fetch group therefore triggering reading of the whole object
nSql = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
} else {
assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
nSql = 1;
}
Employee emp = employees.get(0);
assertFetched(emp, managerFG);
// manager (if not null) is instantiated by the fetch group, before emp.getManager call.
Employee manager = emp.getManager();
assertEquals(nSql, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
assertFetched(manager, managerFG);
// instantiates the whole object
emp.getLastName();
nSql++;
assertNoFetchGroup(emp);
assertEquals(nSql, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
assertFetched(manager, managerFG);
// instantiates the whole object
manager.getLastName();
nSql++;
assertNoFetchGroup(manager);
assertEquals(nSql, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
nSql++;
for (PhoneNumber phone : emp.getPhoneNumbers()) {
assertFetched(phone, defaultPhoneFG);
phone.getAreaCode();
nSql++;
assertNoFetchGroup(phone);
}
assertEquals(nSql, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
nSql++;
for (PhoneNumber phone : manager.getPhoneNumbers()) {
assertFetched(phone, defaultPhoneFG);
phone.getAreaCode();
nSql++;
assertNoFetchGroup(phone);
}
assertEquals(nSql, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void allNestedFetchGroupWithJoinFetch() {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
// select employees who are neither managers nor team leaders
Query query = em.createQuery("SELECT e FROM Employee e WHERE NOT EXISTS(SELECT p.id FROM Project p WHERE p.teamLeader = e) AND NOT EXISTS(SELECT e2.id FROM Employee e2 WHERE e2.manager = e)");
FetchGroup employeeFG = new FetchGroup("employee");
employeeFG.addAttribute("lastName");
employeeFG.addAttribute("address.country");
employeeFG.addAttribute("address.city");
query.setHint(QueryHints.LEFT_FETCH, "e.address");
employeeFG.addAttribute("phoneNumbers");
query.setHint(QueryHints.LEFT_FETCH, "e.phoneNumbers");
employeeFG.addAttribute("projects.name");
employeeFG.addAttribute("projects.teamLeader.firstName");
//employeeFG.addAttribute("projects.teamLeader.address.street");
//employeeFG.addAttribute("projects.teamLeader.address.postalCode");
employeeFG.addAttribute("projects.teamLeader.phoneNumbers.owner");
employeeFG.addAttribute("projects.teamLeader.phoneNumbers.type");
employeeFG.addAttribute("projects.teamLeader.phoneNumbers.areaCode");
query.setHint(QueryHints.LEFT_FETCH, "e.projects.teamLeader.phoneNumbers");
employeeFG.addAttribute("manager.firstName");
//employeeFG.addAttribute("manager.address.street");
//employeeFG.addAttribute("manager.address.postalCode");
employeeFG.addAttribute("manager.phoneNumbers.owner");
employeeFG.addAttribute("manager.phoneNumbers.type");
employeeFG.addAttribute("manager.phoneNumbers.areaCode");
query.setHint(QueryHints.LEFT_FETCH, "e.manager.phoneNumbers");
// department attribute defined with JoinFetchType.OUTER
employeeFG.addAttribute("department.name");
query.setHint(QueryHints.FETCH_GROUP, employeeFG);
List<Employee> employees = query.getResultList();
assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
for(Employee emp :employees) {
assertFetched(emp, employeeFG);
Address address = emp.getAddress();
if(address != null) {
assertFetched(address, employeeFG.getGroup("address"));
}
for (PhoneNumber phone : emp.getPhoneNumbers()) {
assertFetched(phone, defaultPhoneFG);
}
for (Project project : emp.getProjects()) {
assertFetched(project, employeeFG.getGroup("projects"));
Employee teamLeader = project.getTeamLeader();
if(teamLeader != null) {
assertFetched(teamLeader, employeeFG.getGroup("projects.teamLeader"));
for (PhoneNumber phone : teamLeader.getPhoneNumbers()) {
assertFetched(phone, employeeFG.getGroup("projects.teamLeader.phoneNumbers"));
}
}
}
Employee manager = emp.getManager();
if(manager != null) {
assertFetched(manager, employeeFG.getGroup("manager"));
for (PhoneNumber phone : manager.getPhoneNumbers()) {
assertFetched(phone, employeeFG.getGroup("manager.phoneNumbers"));
}
}
Department department = emp.getDepartment();
if(department != null) {
assertFetched(department, employeeFG.getGroup("department"));
}
}
assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void joinFetchDefaultFetchGroup() throws Exception {
EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT e FROM Employee e");
query.setHint(QueryHints.LEFT_FETCH, "e.phoneNumbers");
List<Employee> employees = query.getResultList();
for(Employee emp : employees) {
assertNoFetchGroup(emp);
for (PhoneNumber phone : emp.getPhoneNumbers()) {
assertFetched(phone, defaultPhoneFG);
}
}
}
@Test
public void joinFetchOutsideOfFetchGroup() throws Exception {
EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT e FROM Employee e");
// Define the fields to be fetched on Employee
FetchGroup fg = new FetchGroup();
fg.addAttribute("firstName");
fg.addAttribute("lastName");
query.setHint(QueryHints.FETCH_GROUP, fg);
query.setHint(QueryHints.LEFT_FETCH, "e.address");
List<Employee> employees = query.getResultList();
}
@Test
public void simpleNestedFetchGroupWithBatch() {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e");
// Define the fields to be fetched on Employee
FetchGroup employeeFG = new FetchGroup();
employeeFG.setShouldLoad(true);
employeeFG.addAttribute("firstName");
employeeFG.addAttribute("lastName");
employeeFG.addAttribute("address.country");
employeeFG.addAttribute("address.city");
FetchGroup phonesFG = defaultPhoneFG.clone();
// to preclude PhoneNumber from triggering owner's full read
phonesFG.addAttribute("owner.id");
employeeFG.addAttribute("phoneNumbers", phonesFG);
FetchGroup projectsFG = new FetchGroup("projects");
projectsFG.addAttribute("name");
projectsFG.addAttribute("name");
// to preclude Project from triggering full read of the referenced Employee(s)
projectsFG.addAttribute("teamMembers.id");
projectsFG.addAttribute("teamLeader.id");
employeeFG.addAttribute("projects", projectsFG);
query.setHint(QueryHints.FETCH_GROUP, employeeFG);
query.setHint(QueryHints.BATCH, "e.address");
query.setHint(QueryHints.BATCH, "e.phoneNumbers");
query.setHint(QueryHints.BATCH, "e.projects");
// A single sql will be used to read all Project subclasses.
query.setHint(QueryHints.INHERITANCE_OUTER_JOIN, "true");
List<Employee> employees = query.getResultList();
// Employee, Address, PhoneNumbers, Projects - an sql per class.
// Address, PhoneNumbers and Projects are already loaded because
// employeeFG.shouldLoad is set to true.
assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
// verify fetch groups
for(Employee emp : employees) {
assertFetched(emp, employeeFG);
Address address = emp.getAddress();
if(address != null) {
assertFetched(address, employeeFG.getGroup("address"));
}
for (PhoneNumber phone : emp.getPhoneNumbers()) {
assertFetched(phone, phonesFG);
}
for (Project project : emp.getProjects()) {
assertFetched(project, projectsFG);
}
}
// Now let's access an attribute outside of the fetch group.
// That triggers loading of the whole object.
for(Employee emp : employees) {
emp.getSalary();
assertNoFetchGroup(emp);
Address address = emp.getAddress();
if(address != null) {
address.getStreet();
assertNoFetchGroup(address);
}
for (PhoneNumber phone : emp.getPhoneNumbers()) {
phone.getAreaCode();
assertNoFetchGroup(phone);
}
for (Project project : emp.getProjects()) {
project.getDescription();
assertNoFetchGroup(project);
}
}
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
public void simpleLoadGroup() {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.gender = :GENDER");
query.setParameter("GENDER", Gender.Female);
List<Employee> employees = query.getResultList();
LoadGroup group = new LoadGroup();
group.addAttribute("address");
group.addAttribute("phoneNumbers");
group.addAttribute("manager.projects");
AbstractSession session = (AbstractSession)((EntityManagerImpl)em.getDelegate()).getActiveSession();
session.load(employees, group, session.getClassDescriptor(Employee.class), false);
int numSelectBefore = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
// All indirections specified in the plan should have been already triggered.
for(Employee emp : employees) {
emp.getAddress();
emp.getPhoneNumbers().size();
if(emp.getManager() != null) {
emp.getManager().getProjects().size();
}
}
int numSelectAfter = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
assertEquals(numSelectBefore, numSelectAfter);
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
@Test
// Bug 325684 - QueryHints.BATCH combined with QueryHints.FETCH_GROUP_LOAD will cause NPE
public void simpleFetchGroupLoadWithBatch() {
EntityManager em = createEntityManager();
try {
beginTransaction(em);
FetchGroup projectGroup = new FetchGroup();
projectGroup.addAttribute("name");
FetchGroup employeeGroup = new FetchGroup();
employeeGroup.addAttribute("firstName");
employeeGroup.addAttribute("lastName");
employeeGroup.addAttribute("projects", projectGroup);
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.gender = :GENDER");
query.setParameter("GENDER", Gender.Female);
query.setHint(QueryHints.FETCH_GROUP, employeeGroup);
query.setHint(QueryHints.FETCH_GROUP_LOAD, "true");
query.setHint(QueryHints.BATCH, "e.projects");
List<Employee> employees = query.getResultList();
int numSelectBefore = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
// All indirections specified in the plan should have been already triggered.
for (Employee e : employees) {
e.getProjects().size();
}
int numSelectAfter = getQuerySQLTracker(em).getTotalSQLSELECTCalls();
assertEquals(numSelectBefore, numSelectAfter);
} finally {
if (isTransactionActive(em)){
rollbackTransaction(em);
}
closeEntityManager(em);
}
}
}