| /* |
| * 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); |
| } |
| } |
| } |