blob: 15f7d322aa5fb9d880c887c1265899f50ecc531c [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
package org.eclipse.persistence.testing.tests.jpa.fetchgroups;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import org.eclipse.persistence.config.CacheIsolationType;
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.FetchGroupManager;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.jpa.JpaHelper;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.FetchGroupTracker;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.server.ServerSession;
import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
import org.eclipse.persistence.testing.models.jpa.advanced.AdvancedTableCreator;
import org.eclipse.persistence.testing.models.jpa.advanced.Employee;
import org.eclipse.persistence.testing.models.jpa.advanced.EmployeePopulator;
import org.eclipse.persistence.testing.models.jpa.advanced.EquipmentCode;
import org.eclipse.persistence.testing.models.jpa.advanced.compositepk.CompositePKPopulator;
import org.eclipse.persistence.testing.models.jpa.advanced.compositepk.CompositePKTableCreator;
import org.eclipse.persistence.testing.tests.jpa.dynamic.QuerySQLTracker;
import org.junit.Test;
/**
* Simple set of tests that verify the {@link FetchGroup} API. Need to verify
* that the nesting and default behaves as expected.
*
* @author dclarke
* @since EclipseLink 2.1
*/
public abstract class BaseFetchGroupTests extends JUnitTestCase {
ClassDescriptor employeeDescriptor;
boolean employeeDescriptorIsIsolatedOriginal;
ClassDescriptor phoneDescriptor;
ClassDescriptor addressDescriptor;
int sessionLogLevelOriginal;
public static FetchGroup defaultEmployeeFG;
public static FetchGroup defaultPhoneFG;
public BaseFetchGroupTests() {
super();
}
public BaseFetchGroupTests(String name) {
super(name);
}
/*
* Fetch Group tests require weaving.
*/
@Override
public void runBare() throws Throwable {
if (this.shouldRunTestOnServer()) {
super.runBare();
} else {
if (isWeavingEnabled()) {
super.runBare();
}
}
}
/**
* Any FetchGroups setup in test cases are removed.
* Descriptors should not be isolated.
* Clear cache, install QuerySQLTracker.
*/
@Override
public void setUp() {
Session session = getServerSession();
employeeDescriptor = getDescriptor("Employee");
phoneDescriptor = getDescriptor("PhoneNumber");
addressDescriptor = getDescriptor("Address");
// this causes recreation of the cache removing the previously cached queries
session.getProject().setJPQLParseCacheMaxSize(session.getProject().getJPQLParseCacheMaxSize());
clearFetchGroups(employeeDescriptor);
clearFetchGroups(phoneDescriptor);
clearFetchGroups(addressDescriptor);
// reprepare read queries after all fetch groups are cleared for all descriptors
reprepareReadQueries(employeeDescriptor);
reprepareReadQueries(phoneDescriptor);
reprepareReadQueries(addressDescriptor);
assertConfig(employeeDescriptor, null, 0);
assertConfig(addressDescriptor, null, 0);
assertConfig(phoneDescriptor, null, 0);
employeeDescriptorIsIsolatedOriginal = employeeDescriptor.isIsolated();
if(employeeDescriptorIsIsolatedOriginal) {
employeeDescriptor.setCacheIsolation(CacheIsolationType.ISOLATED);
}
clearCache();
QuerySQLTracker.install(session);
sessionLogLevelOriginal = session.getLogLevel();
if(sessionLogLevelOriginal > SessionLog.FINE) {
session.setLogLevel(SessionLog.FINE);
}
}
/**
* Any FetchGroups setup in test cases are removed.
* Reset isolated flag on Employee descriptor.
* Clear cache, uninstall QuerySQLTracker.
*/
@Override
public void tearDown() {
Session session = getServerSession();
// this causes recreation of the cache removing the previously cached queries
session.getProject().setJPQLParseCacheMaxSize(session.getProject().getJPQLParseCacheMaxSize());
clearFetchGroups(employeeDescriptor);
clearFetchGroups(phoneDescriptor);
clearFetchGroups(addressDescriptor);
// reprepare read queries after all fetch groups are cleared for all descriptors
clearReadQueries(employeeDescriptor);
clearReadQueries(phoneDescriptor);
clearReadQueries(addressDescriptor);
if(employeeDescriptorIsIsolatedOriginal) {
employeeDescriptor.setCacheIsolation(CacheIsolationType.ISOLATED);
}
clearCache();
if(sessionLogLevelOriginal != session.getLogLevel()) {
session.setLogLevel(sessionLogLevelOriginal);
}
QuerySQLTracker.uninstall(session);
}
void clearFetchGroups(ClassDescriptor descriptor) {
FetchGroupManager manager = descriptor.getFetchGroupManager();
if(manager != null) {
if(manager.getDefaultFetchGroup() != null) {
manager.setDefaultFetchGroup(null);
}
if(manager.getFetchGroups().size() > 0) {
manager.getFetchGroups().clear();
}
}
}
void reprepareReadQueries(ClassDescriptor descriptor) {
reprepareReadQueriesInternal(descriptor, false);
}
void clearReadQueries(ClassDescriptor descriptor) {
reprepareReadQueriesInternal(descriptor, true);
}
private void reprepareReadQueriesInternal(ClassDescriptor descriptor, boolean shouldClear) {
ObjectLevelReadQuery olrQuery;
AbstractSession session = getServerSession();
if(descriptor.getQueryManager().hasReadObjectQuery()) {
// this un-prePrepares the query, causes executionFetchGroup to be rebuilt
olrQuery = descriptor.getQueryManager().getReadObjectQuery();
if(shouldClear) {
olrQuery.setFetchGroupName(null);
olrQuery.setShouldUseDefaultFetchGroup(true);
} else {
olrQuery.setShouldUseDefaultFetchGroup(olrQuery.shouldUseDefaultFetchGroup());
}
descriptor.getQueryManager().getReadObjectQuery().checkPrepare(session, null);
}
if(descriptor.getQueryManager().hasReadAllQuery()) {
// this un-prePrepares the query, causes executionFetchGroup to be rebuilt
olrQuery = descriptor.getQueryManager().getReadAllQuery();
if(shouldClear) {
olrQuery.setFetchGroupName(null);
olrQuery.setShouldUseDefaultFetchGroup(true);
} else {
olrQuery.setShouldUseDefaultFetchGroup(olrQuery.shouldUseDefaultFetchGroup());
}
olrQuery.checkPrepare(session, null);
}
// this causes recreation of the cache removing the previously cached queries
descriptor.getQueryManager().setExpressionQueryCacheMaxSize(descriptor.getQueryManager().getExpressionQueryCacheMaxSize());
for(DatabaseMapping mapping : descriptor.getMappings()) {
if(mapping.isForeignReferenceMapping()) {
if(((ForeignReferenceMapping)mapping).getSelectionQuery().isObjectLevelReadQuery()) {
olrQuery = (ObjectLevelReadQuery)((ForeignReferenceMapping)mapping).getSelectionQuery();
// this un-prePrepares the query, causes executionFetchGroup to be rebuilt
if(shouldClear) {
olrQuery.setFetchGroupName(null);
olrQuery.setShouldUseDefaultFetchGroup(true);
} else {
olrQuery.setShouldUseDefaultFetchGroup(olrQuery.shouldUseDefaultFetchGroup());
}
olrQuery.checkPrepare(session, null);
}
}
}
}
@Test
public void testSetup() {
ServerSession session = getServerSession();
new AdvancedTableCreator().replaceTables(session);
//requires another package
new CompositePKTableCreator().replaceTables(session);
// Force uppercase for Postgres.
if (session.getPlatform().isPostgreSQL()) {
session.getLogin().setShouldForceFieldNamesToUpperCase(true);
}
// The EquipmentCode class 'should' be set to read only. We want
// to be able to create a couple in the Employee populator, so
// force the read only to false. If EquipmentCode is not
// actually read only, don't worry, we set the original read
// only value back on the descriptor and the error will be
// caught in a later test in this suite.
ClassDescriptor descriptor = session.getDescriptor(EquipmentCode.class);
boolean shouldBeReadOnly = descriptor.shouldBeReadOnly();
descriptor.setShouldBeReadOnly(false);
// Populate the database with our examples.
EmployeePopulator employeePopulator = new EmployeePopulator();
employeePopulator.buildExamples();
employeePopulator.persistExample(session);
CompositePKPopulator compositePKPopulator = new CompositePKPopulator();
compositePKPopulator.buildExamples();
compositePKPopulator.persistExample(session);
descriptor.setShouldBeReadOnly(shouldBeReadOnly);
clearCache();
}
public void managerFetchGroup() throws Exception {
EntityManager em = createEntityManager();
// Use q query since find will only use default fetch group
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
query.setParameter("ID", minimumEmployeeId(em));
FetchGroup managerFG = new FetchGroup();
managerFG.addAttribute("manager");
query.setHint(QueryHints.FETCH_GROUP, managerFG);
assertNotNull(getFetchGroup(query));
assertSame(managerFG, getFetchGroup(query));
Employee emp = (Employee) query.getSingleResult();
assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
assertFetchedAttribute(emp, "id");
assertNotFetchedAttribute(emp, "firstName");
assertFetchedAttribute(emp, "version");
assertFetchedAttribute(emp, "manager");
assertFetchedAttribute(emp, "address");
assertFetchedAttribute(emp, "phoneNumbers");
assertFetchedAttribute(emp, "projects");
emp.getManager();
assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
emp.getLastName();
assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
}
public void employeeNamesFetchGroup() throws Exception {
EntityManager em = createEntityManager();
int minId = minimumEmployeeId(em);
assertEquals(1, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
// Use q query since find will only use default fetch group
Query query = em.createQuery("SELECT e FROM Employee e WHERE e.id = :ID");
query.setParameter("ID", minId);
FetchGroup namesFG = new FetchGroup();
namesFG.addAttribute("firstName");
namesFG.addAttribute("lastName");
query.setHint(QueryHints.FETCH_GROUP, namesFG);
assertNotNull(getFetchGroup(query));
assertSame(namesFG, getFetchGroup(query));
Employee emp = (Employee) query.getSingleResult();
assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
assertFetchedAttribute(emp, "id");
assertFetchedAttribute(emp, "firstName");
assertFetchedAttribute(emp, "lastName");
assertFetchedAttribute(emp, "gender");
assertFetchedAttribute(emp, "salary");
assertFetchedAttribute(emp, "version");
assertFetchedAttribute(emp, "manager");
assertFetchedAttribute(emp, "address");
assertFetchedAttribute(emp, "phoneNumbers");
assertFetchedAttribute(emp, "projects");
emp.getId();
emp.getFirstName();
emp.getLastName();
emp.getVersion();
assertEquals(2, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
emp.getGender();
assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
assertFetchedAttribute(emp, "gender");
assertFetchedAttribute(emp, "salary");
emp.getSalary();
assertEquals(3, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
emp.getManager();
assertEquals(4, getQuerySQLTracker(em).getTotalSQLSELECTCalls());
assertFetchedAttribute(emp, "manager");
}
public void joinFetchEmployeeAddressWithDynamicFetchGroup() {
EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT e FROM Employee e JOIN FETCH e.address");
FetchGroup fetchGroup = new FetchGroup("names");
fetchGroup.addAttribute("firstName");
fetchGroup.addAttribute("lastName");
query.setHint(QueryHints.FETCH_GROUP, fetchGroup);
List<Employee> emps = query.getResultList();
assertNotNull(emps);
}
public void joinFetchEmployeeAddressPhoneWithDynamicFetchGroup() {
EntityManager em = createEntityManager();
Query query = em.createQuery("SELECT e FROM Employee e JOIN FETCH e.address WHERE e.id IN (SELECT p.id FROM PhoneNumber p)");
FetchGroup fetchGroup = new FetchGroup("names");
fetchGroup.addAttribute("firstName");
fetchGroup.addAttribute("lastName");
query.setHint(QueryHints.FETCH_GROUP, fetchGroup);
List<Employee> emps = query.getResultList();
assertNotNull(emps);
}
protected Employee findMinimumEmployee(EntityManager em) {
List<Employee> emps = em.createQuery("SELECT e FROM Employee e WHERE e.id in (SELECT MIN(ee.id) FROM Employee ee)").getResultList();
assertNotNull("Null returned for min employee query", emps);
assertEquals("No results returned for Mmin employee query", 1, emps.size());
return emps.get(0);
}
public FetchGroup assertHasFetchGroup(Object entity) {
assertNotNull("Entity is null", entity);
assertTrue("Entity does not implement FetchGroupTracker", entity instanceof FetchGroupTracker);
assertNotNull("Entity does not have FetchGroup", ((FetchGroupTracker) entity)._persistence_getFetchGroup());
return ((FetchGroupTracker) entity)._persistence_getFetchGroup();
}
protected FetchGroup getFetchGroup(Object object) {
assertNotNull("Cannot get a FetchGroup from null", object);
if (object instanceof Query) {
return getFetchGroup((Query) object);
}
if (object instanceof ObjectLevelReadQuery) {
return getExecutionFetchGroup((ObjectLevelReadQuery) object);
}
assertTrue("Entity " + object + " does not implement FetchGroupTracker", object instanceof FetchGroupTracker);
FetchGroupTracker tracker = (FetchGroupTracker) object;
return tracker._persistence_getFetchGroup();
}
protected FetchGroup getFetchGroup(Query query) {
return getFetchGroup((ObjectLevelReadQuery)JpaHelper.getDatabaseQuery(query));
}
protected FetchGroup getFetchGroup(ObjectLevelReadQuery readQuery) {
return readQuery.getFetchGroup();
}
protected FetchGroup getExecutionFetchGroup(Query query) {
return getExecutionFetchGroup((ObjectLevelReadQuery)JpaHelper.getDatabaseQuery(query));
}
protected FetchGroup getExecutionFetchGroup(ObjectLevelReadQuery readQuery) {
return readQuery.getExecutionFetchGroup();
}
public void assertFetchedAttribute(Object entity, String... attribute) {
FetchGroupAssert.assertFetchedAttribute(getEntityManagerFactory(), entity, attribute);
}
public void assertNotFetchedAttribute(Object entity, String... attribute) {
FetchGroupAssert.assertNotFetchedAttribute(getEntityManagerFactory(), entity, attribute);
}
public void assertFetched(Object entity, FetchGroup fetchGroup) {
FetchGroupAssert.assertFetched(getEntityManagerFactory(), entity, fetchGroup);
}
public void assertDefaultFetched(Object entity) {
FetchGroupAssert.assertDefaultFetched(getEntityManagerFactory(), entity);
}
public void assertFetched(Object entity, String fetchGroupName) {
FetchGroupAssert.assertFetched(getEntityManagerFactory(), entity, fetchGroupName);
}
public void assertNoFetchGroup(Object entity) {
FetchGroupAssert.assertNoFetchGroup(getEntityManagerFactory(), entity);
}
public void assertConfig(String entityName, FetchGroup defaultFetchGroup) {
FetchGroupAssert.assertConfig(getEntityManagerFactory(), entityName, defaultFetchGroup);
}
public void assertConfig(String entityName, FetchGroup defaultFetchGroup, int numNamedFetchGroups) {
FetchGroupAssert.assertConfig(getEntityManagerFactory(), entityName, defaultFetchGroup, numNamedFetchGroups);
}
public void assertConfig(ClassDescriptor descriptor, FetchGroup defaultFetchGroup) {
FetchGroupAssert.assertConfig(descriptor, defaultFetchGroup);
}
public void assertConfig(ClassDescriptor descriptor, FetchGroup defaultFetchGroup, int numNamedFetchGroups) {
FetchGroupAssert.assertConfig(descriptor, defaultFetchGroup, numNamedFetchGroups);
}
protected QuerySQLTracker getQuerySQLTracker(EntityManager em) {
return QuerySQLTracker.getTracker(getServerSession());
}
ClassDescriptor getDescriptor(String entityName) {
return getServerSession().getClassDescriptorForAlias(entityName);
}
public static int minimumEmployeeId(EntityManager em) {
return ((Number) em.createQuery("SELECT MIN(e.id) FROM Employee e").getSingleResult()).intValue();
}
public static int minimumEmployeeWithoutDepartmentId(EntityManager em) {
return ((Number) em.createQuery("SELECT MIN(e.id) FROM Employee e WHERE e.department IS NULL").getSingleResult()).intValue();
}
public static Employee minimumEmployee(EntityManager em) {
Query q = em.createQuery("SELECT e FROM Employee e WHERE e.id in (SELECT MIN(ee.id) FROM Employee ee)");
return (Employee) q.getSingleResult();
}
public static Employee minimumEmployee(EntityManager em, Map<String, Object> hints) {
Query q = em.createQuery("SELECT e FROM Employee e WHERE e.id in (SELECT MIN(ee.id) FROM Employee ee)");
Iterator<Map.Entry<String, Object>> it = hints.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, Object> entry = it.next();
q.setHint(entry.getKey(), entry.getValue());
}
return (Employee) q.getSingleResult();
}
public static Employee minEmployeeWithAddressAndPhones(EntityManager em) {
return (Employee) em.createQuery("SELECT e FROM Employee e JOIN FETCH e.address WHERE e.id IN (SELECT MIN(p.id) FROM PhoneNumber p)").getSingleResult();
}
public Employee minEmployeeWithManagerWithAddress(EntityManager em) {
List<Employee> emps = em.createQuery("SELECT e FROM Employee e JOIN FETCH e.manager WHERE e.manager.address IS NOT NULL ORDER BY e.id").getResultList();
return emps.get(0);
}
public static int minEmployeeIdWithAddressAndPhones(EntityManager em) {
return ((Number) em.createQuery("SELECT e.id FROM Employee e JOIN FETCH e.address WHERE e.id IN (SELECT MIN(p.id) FROM PhoneNumber p)").getSingleResult()).intValue();
}
public static class PhoneCustomizer implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) throws Exception {
defaultPhoneFG = new FetchGroup("PhoneNumber.default");
defaultPhoneFG.addAttribute("number");
descriptor.getFetchGroupManager().setDefaultFetchGroup(defaultPhoneFG);
}
}
}