From 384556ffb8a691b39ddfd198aa80859c0e707699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Fri, 24 Nov 2023 17:47:29 +0100 Subject: [PATCH] jakartaee/persistence#447 - add new operations to PersistenceUnitUtil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../jpa.test.persistence32/pom.xml | 28 +++ .../models/jpa/persistence32/Pokemon.java | 10 +- .../models/jpa/persistence32/Trainer.java | 11 +- .../jpa/persistence32/AbstractPokemon.java | 8 +- .../EntityManagerFactoryTest.java | 143 +++++++++++++- .../persistence32/UnionCriteriaQueryTest.java | 4 + .../jpa/EntityManagerFactoryDelegate.java | 15 +- .../jpa/EntityManagerFactoryImpl.java | 182 ++++++++++++++---- .../jpa/querydef/AbstractQueryImpl.java | 18 +- .../querydef/CommonAbstractCriteriaImpl.java | 10 + .../jpa/querydef/CriteriaQueryImpl.java | 13 +- .../internal/jpa/querydef/SubQueryImpl.java | 50 +++-- 12 files changed, 422 insertions(+), 70 deletions(-) diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml index 1fab94c4fdd..d9f5d3e2480 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml @@ -80,6 +80,34 @@ + + + org.bsc.maven + maven-processor-plugin + 4.5 + + + eclipselink-jpa-metamodel + + process + + process-resources + + + org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor + + ${project.build.directory}/generated-sources/meta-model + + + + + + org.eclipse.persistence + org.eclipse.persistence.jpa.modelgen.processor + ${project.version} + + + diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java index 79833f960f4..78974b3b429 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java @@ -24,12 +24,18 @@ import jakarta.persistence.NamedNativeQuery; import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; +import org.eclipse.persistence.annotations.FetchAttribute; +import org.eclipse.persistence.annotations.FetchGroup; +import org.eclipse.persistence.annotations.FetchGroups; @Entity @Table(name="PERSISTENCE32_POKEMON") @NamedQuery(name="Pokemon.get", query="SELECT p FROM Pokemon p WHERE p.id = :id") @NamedNativeQuery(name="Pokemon.deleteAllTypes", query="DELETE FROM PERSISTENCE32_POKEMON_TYPE") @NamedNativeQuery(name="Pokemon.deleteAll", query="DELETE FROM PERSISTENCE32_POKEMON") +@FetchGroups({ + @FetchGroup(name = "FetchTypes", attributes = {@FetchAttribute(name = "types")}) +}) public class Pokemon { // ID is assigned in tests to avoid collisions @@ -63,8 +69,10 @@ public Pokemon(String name, Trainer trainer, Collection types) { this.types = types; } + // Required for testUnionWithMultiselectEntityParametersInSelection + // to select ResultType.CONSTRUCTOR for the query. public Pokemon(int id, String name) { - this(name, null, Collections.EMPTY_LIST); + this(name, null, Collections.emptyList()); this.id = id; } diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Trainer.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Trainer.java index 1ac8f25ade5..0a57fe15105 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Trainer.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Trainer.java @@ -16,17 +16,13 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.NamedNativeQuery; import jakarta.persistence.NamedQuery; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import org.eclipse.persistence.annotations.JoinFetch; -import org.eclipse.persistence.annotations.JoinFetchType; -import org.eclipse.persistence.annotations.Property; -import static jakarta.persistence.FetchType.EAGER; +import static jakarta.persistence.FetchType.LAZY; @Entity @Table(name="PERSISTENCE32_TRAINER") @@ -40,7 +36,7 @@ public class Trainer { private String name; - @ManyToOne + @ManyToOne(fetch = LAZY) private Team team; @OneToMany(mappedBy = "trainer") @@ -98,8 +94,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - int result = Objects.hash(id, name); - return result; + return Objects.hash(id, name); } } diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java index 6711105f4fa..c3a767ed9f8 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java @@ -129,7 +129,6 @@ Map pokemonTypes(EntityManager em) { */ public void testSetup() { new Persistence32TableCreator().replaceTables(JUnitTestCase.getServerSession(getPersistenceUnitName())); - clearCache(); emf.runInTransaction(em -> { for (int i = 1; i < TEAMS.length; i++) { em.persist(TEAMS[i]); @@ -141,6 +140,7 @@ public void testSetup() { em.persist(TYPES[i]); } }); + clearCache(); } /** @@ -157,4 +157,10 @@ public void testCleanup() { }); } + @Override + public void clearCache() { + emf.getCache().evictAll(); + super.clearCache(); + } + } diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java index daf99c82562..dd5e7f3925b 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java @@ -11,9 +11,11 @@ */ package org.eclipse.persistence.testing.tests.jpa.persistence32; +import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,9 +26,15 @@ import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceException; import jakarta.persistence.PersistenceUnitTransactionType; +import jakarta.persistence.PersistenceUnitUtil; import junit.framework.Test; +import org.eclipse.persistence.config.QueryHints; +import org.eclipse.persistence.descriptors.ClassDescriptor; +import org.eclipse.persistence.descriptors.FetchGroupManager; import org.eclipse.persistence.jpa.JpaEntityManagerFactory; import org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon; +import org.eclipse.persistence.testing.models.jpa.persistence32.Trainer; +import org.eclipse.persistence.testing.models.jpa.persistence32.Trainer_; import org.eclipse.persistence.testing.models.jpa.persistence32.Type; public class EntityManagerFactoryTest extends AbstractPokemon { @@ -40,7 +48,15 @@ public static Test suite() { new EntityManagerFactoryTest("testCallWithConnection"), new EntityManagerFactoryTest("testCreateCustomEntityManagerFactory"), new EntityManagerFactoryTest("testCreateConflictingCustomEntityManagerFactory"), - new EntityManagerFactoryTest("testCreateConflictingConfiguredEntityManagerFactory")); + new EntityManagerFactoryTest("testCreateConflictingConfiguredEntityManagerFactory"), + new EntityManagerFactoryTest("testIsLoadedEntityAttribute"), + new EntityManagerFactoryTest("testLoadEntityAttribute"), + new EntityManagerFactoryTest("testIsLoadedEntityNamedAttribute"), + new EntityManagerFactoryTest("testLoadEntityNamedAttribute"), + new EntityManagerFactoryTest("testVerifyPokemonFetchGroups"), + new EntityManagerFactoryTest("testIsLoadedEntity"), + new EntityManagerFactoryTest("testLoadEntity") + ); } public EntityManagerFactoryTest() { @@ -209,6 +225,113 @@ public void testCreateConflictingConfiguredEntityManagerFactory() { } } + public void testIsLoadedEntityAttribute() { + Trainer t = emf.callInTransaction( + em -> em.createQuery("SELECT t FROM Trainer t WHERE t.name = :name", Trainer.class) + .setParameter("name", "Ash") + .getSingleResult()); + PersistenceUnitUtil util = emf.getPersistenceUnitUtil(); + // This mapping is lazy, so it shall not be loaded + assertFalse("Entity lazy attribute Trainer.team should not be loaded", util.isLoaded(t, Trainer_.team)); + } + + public void testLoadEntityAttribute() { + Trainer t = emf.callInTransaction( + em -> em.createQuery("SELECT t FROM Trainer t WHERE t.name = :name", Trainer.class) + .setParameter("name", "Ash") + .getSingleResult()); + PersistenceUnitUtil util = emf.getPersistenceUnitUtil(); + // This mapping is lazy, so it shall not be loaded + assertFalse("Entity lazy attribute Trainer.team should not be loaded", util.isLoaded(t, Trainer_.team)); + util.load(t, Trainer_.team); + assertTrue("Entity lazy attribute Trainer.team should be loaded after load call", util.isLoaded(t, Trainer_.team)); + } + + public void testIsLoadedEntityNamedAttribute() { + Trainer t = emf.callInTransaction( + em -> em.createQuery("SELECT t FROM Trainer t WHERE t.name = :name", Trainer.class) + .setParameter("name", "Ash") + .getSingleResult()); + PersistenceUnitUtil util = emf.getPersistenceUnitUtil(); + // This mapping is lazy, so it shall not be loaded + assertFalse("Entity lazy attribute Trainer.team should not be loaded", util.isLoaded(t, "team")); + } + + public void testLoadEntityNamedAttribute() { + Trainer t = emf.callInTransaction( + em -> em.createQuery("SELECT t FROM Trainer t WHERE t.name = :name", Trainer.class) + .setParameter("name", "Ash") + .getSingleResult()); + PersistenceUnitUtil util = emf.getPersistenceUnitUtil(); + // This mapping is lazy, so it shall not be loaded + assertFalse("Entity lazy attribute Trainer.team should not be loaded", + util.isLoaded(t, "team")); + util.load(t, "team"); + assertTrue("Entity lazy attribute Trainer.team should be loaded after load call", + util.isLoaded(t, "team")); + } + + public void testVerifyPokemonFetchGroups() { + if (isWeavingEnabled()) { + ClassDescriptor pokemonsDescriptor = getPersistenceUnitServerSession().getDescriptor(Pokemon.class); + FetchGroupManager pokemonsFetchGroupManager = pokemonsDescriptor.getFetchGroupManager(); + assertEquals("Wrong number of fetch groups for Pokemon", 1, pokemonsFetchGroupManager.getFetchGroups().size()); + assertNotNull("The 'FetchTypes' fetch group was not found for Pokemon", pokemonsFetchGroupManager.getFetchGroup("FetchTypes")); + } + } + + public void testIsLoadedEntity() { + if (isWeavingEnabled()) { + Pokemon ekans = new Pokemon(6, TRAINERS[2], "Ekans", List.of(TYPES[4])); + emf.runInTransaction(em -> em.persist(ekans)); + clearCache(); + Map properties = new HashMap<>(); + properties.put(QueryHints.FETCH_GROUP_NAME, "FetchTypes"); + Class pokemonClass = Pokemon.class; + EntityManager em = createEntityManager(); + try { + Pokemon pokemon = em.find(pokemonClass, ekans.getId(), properties); + verifyFetchedField(pokemonClass.getDeclaredField("types"), pokemon, ekans.getTypes()); + verifyNonFetchedField(pokemonClass.getDeclaredField("name"), pokemon); + verifyNonFetchedField(pokemonClass.getDeclaredField("trainer"), pokemon); + PersistenceUnitUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil(); + // This mapping is lazy, so it shall not be loaded + assertFalse("Pokemon lazy attributes name and trainer should not be loaded", util.isLoaded(pokemon)); + } catch (Exception e) { + fail("Error verifying field content: " + e.getMessage()); + } finally { + closeEntityManager(em); + } + } + } + + public void testLoadEntity() { + if (isWeavingEnabled()) { + Pokemon arbok = new Pokemon(7, TRAINERS[2], "Arbok", List.of(TYPES[4])); + emf.runInTransaction(em -> em.persist(arbok)); + clearCache(); + Map properties = new HashMap<>(); + properties.put(QueryHints.FETCH_GROUP_NAME, "FetchTypes"); + Class pokemonClass = Pokemon.class; + EntityManager em = createEntityManager(); + try { + Pokemon pokemon = em.find(pokemonClass, arbok.getId(), properties); + verifyFetchedField(pokemonClass.getDeclaredField("types"), pokemon, arbok.getTypes()); + verifyNonFetchedField(pokemonClass.getDeclaredField("name"), pokemon); + verifyNonFetchedField(pokemonClass.getDeclaredField("trainer"), pokemon); + PersistenceUnitUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil(); + // This mapping is lazy, so it shall not be loaded + assertFalse("Pokemon lazy attributes name and trainer should not be loaded", util.isLoaded(pokemon)); + util.load(pokemon); + assertTrue("Pokemon lazy attributes name and trainer should be loaded after load call", util.isLoaded(pokemon)); + } catch (Exception e) { + fail("Error verifying field content: " + e.getMessage()); + } finally { + closeEntityManager(em); + } + } + } + private static PersistenceConfiguration createPersistenceConfiguration(JpaEntityManagerFactory emf, String puName) { PersistenceConfiguration configuration = new PersistenceConfiguration(puName); configuration.properties(emf.getProperties()); @@ -221,4 +344,22 @@ private static PersistenceConfiguration createPersistenceConfiguration(JpaEntity return configuration; } + private static void verifyFetchedField(Field field, Object obj, Object value) { + try { + field.setAccessible(true); + assertEquals("The field [" + field.getName() + "] was not fetched", field.get(obj), value); + } catch (IllegalAccessException e) { + fail("Error verifying field content: " + e.getMessage()); + } + } + + private static void verifyNonFetchedField(Field field, Object obj) { + try { + field.setAccessible(true); + assertNull("The field [" + field.getName() + "] was fetched", field.get(obj)); + } catch (IllegalAccessException e) { + fail("Error verifying field content: " + e.getMessage()); + } + } + } diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java index 0fea3a00db1..4ec968f5856 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java @@ -384,6 +384,7 @@ public void testUnionWithEntityParameterInSelection() { } } + // Requires Pokemon(int id, String name) constructor to set ResultType.CONSTRUCTOR in multiselect call @SuppressWarnings("deprecation") public void testUnionWithMultiselectEntityParametersInSelection() { try (EntityManager em = emf.createEntityManager()) { @@ -414,6 +415,9 @@ public void testUnionWithMultiselectEntityParametersInSelection() { } } + // TODO testUnionWithMultiselectEntityParametersInSelection without supporting constructor + // to validate ResultType.PARTIAL with compound query + public void testUnionWithMultiselectEntityInSelection() { try (EntityManager em = emf.createEntityManager()) { EntityTransaction et = em.getTransaction(); diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java index 4a8b2c79d5a..ecf6e031a0f 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java @@ -739,28 +739,24 @@ public boolean isLoaded(Object entity) { EntityManagerFactoryImpl.isLoaded(entity, session)); } - // TODO-API-3.2 @Override public void load(Object entity, String attributeName) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + EntityManagerFactoryImpl.load(entity, attributeName, session); } - // TODO-API-3.2 @Override public void load(E entity, Attribute attribute) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + EntityManagerFactoryImpl.load(entity, attribute.getName(), session); } - // TODO-API-3.2 @Override public void load(Object entity) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + EntityManagerFactoryImpl.load(entity, session); } - // TODO-API-3.2 @Override public boolean isInstance(Object entity, Class entityClass) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return EntityManagerFactoryImpl.isInstance(entity, entityClass, session); } @Override @@ -768,10 +764,9 @@ public String getName() { return setupImpl.getPersistenceUnitUniqueName(); } - // TODO-API-3.2 @Override public Class getClass(T entity) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + return EntityManagerFactoryImpl.getClass(entity, session); } /** diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java index 7d624f5d791..a4614f43111 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java @@ -25,7 +25,6 @@ package org.eclipse.persistence.internal.jpa; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -88,13 +87,14 @@ public class EntityManagerFactoryImpl implements EntityManagerFactory, Persisten * the entity does not yet have an id * * @return id of the entity - * @throws IllegalArgumentException - * if the entity is found not to be an entity. + * @throws IllegalArgumentException if the given object is not an instance + * of an entity class belonging to the persistence unit */ public static Object getIdentifier(Object entity, AbstractSession session) { ClassDescriptor descriptor = session.getDescriptor(entity); if (descriptor == null) { - throw new IllegalArgumentException(ExceptionLocalization.buildMessage("jpa_persistence_util_non_persistent_class", new Object[] { entity })); + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "jpa_persistence_util_non_persistent_class", new Object[] { entity })); } if (descriptor.getCMPPolicy() != null) { return descriptor.getCMPPolicy().createPrimaryKeyInstance(entity, session); @@ -104,19 +104,94 @@ public static Object getIdentifier(Object entity, AbstractSession session) { } } + /** + * Load the persistent state of an entity belonging to the + * persistence unit and to an open persistence context. + * After this method returns, {@link #isLoaded(Object, AbstractSession)} must + * return true with the given entity instance. + * + * @param entity entity instance to be loaded + * @param session database session + * @throws IllegalArgumentException if the given object is not an instance + * of an entity class belonging to the persistence unit + */ + static void load(Object entity, AbstractSession session) { + ClassDescriptor descriptor = session.getDescriptor(entity); + if (descriptor != null) { + List mappings = descriptor.getMappings(); + for (DatabaseMapping mapping : mappings) { + if (!mapping.isLazy() && !isLoaded(entity, mapping.getAttributeName(), mapping)) { + load(entity, mapping.getAttributeName(), mapping); + } + } + } else { + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "jpa_persistence_util_non_persistent_class", new Object[] {entity})); + } + } /** - * Determine the load state of an entity belonging to the persistence unit. - * This method can be used to determine the load state of an entity passed - * as a reference. An entity is considered loaded if all attributes for - * which FetchType EAGER has been specified have been loaded. The - * isLoaded(Object, String) method should be used to determine the load - * state of an attribute. Not doing so might lead to unintended loading of - * state. + * Load the persistent value of a given persistent attribute + * of an entity belonging to the persistence unit and to an + * open persistence context. + * After this method returns, {@link #isLoaded(Object, String, AbstractSession)} + * must return true with the given entity instance and attribute. * - * @param entity - * whose load state is to be determined - * @return false if the entity has not been loaded, else true. + * @param entity entity instance to be loaded + * @param attributeName the name of the attribute to be loaded + * @param session database session + * @throws IllegalArgumentException if the given object is not an instance + * of an entity class belonging to the persistence unit + */ + static void load(Object entity, String attributeName, AbstractSession session) { + ClassDescriptor descriptor = session.getDescriptor(entity); + if (descriptor != null) { + DatabaseMapping mapping = descriptor.getMappingForAttributeName(attributeName); + if (mapping != null) { + load(entity, attributeName, mapping); + } + } else { + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "jpa_persistence_util_non_persistent_class", new Object[] {entity})); + } + } + + /** + * Load the persistent value of a given persistent attribute + * of an entity belonging to the persistence unit with a given mapping. + * + * @param entity entity instance to be loaded + * @param attributeName the name of the attribute to be loaded + * @param mapping database mapping metadata of the attribute + */ + private static void load(Object entity, String attributeName, DatabaseMapping mapping) { + if (mapping.isForeignReferenceMapping()) { + Object value = mapping.getAttributeValueFromObject(entity); + IndirectionPolicy policy = ((ForeignReferenceMapping) mapping).getIndirectionPolicy(); + if (!policy.objectIsInstantiated(value)) { + policy.instantiateObject(entity, value); + } + } else if (entity instanceof FetchGroupTracker tracker) { + if (!tracker._persistence_isAttributeFetched(attributeName)) { + EntityManagerImpl.processUnfetchedAttribute(tracker, attributeName); + } + } + } + + /** + * Determine the load state of an entity belonging to the + * persistence unit. This method can be used to determine the + * load state of an entity passed as a reference. An entity is + * considered loaded if all attributes for which + * {@link jakarta.persistence.FetchType#EAGER} has been specified have been loaded. + *

The {@link #isLoaded(Object, String)} method should be + * used to determine the load state of an attribute. Not doing + * so might lead to unintended loading of state. + * + * @param entity entity instance whose load state is to be determined + * @param session database session + * @return Value of {@code true} if the given entity has been loaded + * or {@code false} otherwise. */ public static Boolean isLoaded(Object entity, AbstractSession session) { ClassDescriptor descriptor = session.getDescriptor(entity); @@ -124,9 +199,7 @@ public static Boolean isLoaded(Object entity, AbstractSession session) { return null; } List mappings = descriptor.getMappings(); - Iterator i = mappings.iterator(); - while (i.hasNext()) { - DatabaseMapping mapping = i.next(); + for (DatabaseMapping mapping : mappings) { if (!mapping.isLazy() && !isLoaded(entity, mapping.getAttributeName(), mapping)) { return false; } @@ -134,17 +207,15 @@ public static Boolean isLoaded(Object entity, AbstractSession session) { return true; } - /** - * Determine the load state of a given persistent attribute of an entity - * belonging to the persistence unit. + * Determine the load state of a given persistent attribute + * of an entity belonging to the persistence unit. * - * @param entity - * containing the attribute - * @param attributeName - * name of attribute whose load state is to be determined - * @return false if entity's state has not been loaded or if the attribute - * state has not been loaded, otherwise true + * @param entity entity instance containing the attribute + * @param attributeName name of attribute whose load state is to be determined + * @param session database session + * @return Value of {@code true} if the given attribute has been loaded + * or {@code false} otherwise. */ public static Boolean isLoaded(Object entity, String attributeName, AbstractSession session) { ClassDescriptor descriptor = session.getDescriptor(entity); @@ -166,11 +237,15 @@ public static Boolean isLoaded(Object entity, String attributeName, AbstractSess /** * Check whether a named attribute on a given entity with a given mapping * has been loaded. - * - * This method will check the valueholder or indirect collection for LAZY + * This method will check the value holder or indirect collection for LAZY * ForeignReferenceMappings to see if has been instantiated and otherwise * check the fetch group. * + * @param entity entity instance containing the attribute + * @param attributeName name of attribute whose load state is to be determined + * @param mapping database mapping metadata of the attribute + * @return Value of {@code true} if the given attribute has been loaded + * or {@code false} otherwise. */ public static boolean isLoaded(Object entity, String attributeName, DatabaseMapping mapping) { if (mapping.isForeignReferenceMapping()) { @@ -188,6 +263,52 @@ public static boolean isLoaded(Object entity, String attributeName, DatabaseMapp } } + /** + * Check whether the given entity belonging to the persistence + * unit and to an open persistence context is an instance of the + * given entity class, or false otherwise. + * This method may, but is not required to, load the given entity + * by side effect. + * + * @param entity entity instance + * @param entityClass an entity class belonging to the persistence unit + * @param session database session + * @return Value of {@code true} if the given entity is an instance + * of the given entity class or {@code false} otherwise. + * @throws IllegalArgumentException if the given object is not an instance + * of an entity class belonging to the persistence unit + */ + static boolean isInstance(Object entity, Class entityClass, AbstractSession session) { + // Just validate that entity belongs to current PU + ClassDescriptor descriptor = session.getDescriptor(entity); + if (descriptor == null) { + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "jpa_persistence_util_non_persistent_class", new Object[] {entity})); + } + return entityClass.isInstance(entity); + } + + /** + * Return the concrete entity class if the given entity belonging + * to the persistence unit and to an open persistence context. + * This method may, but is not required to, load the given entity + * by side effect. + * + * @param entity entity instance + * @param session database session + * @return an entity class belonging to the persistence unit + * @throws IllegalArgumentException if the given object is not an instance + * of an entity class belonging to the persistence unit + */ + static Class getClass(T entity, AbstractSession session) { + ClassDescriptor descriptor = session.getDescriptor(entity); + if (descriptor == null) { + throw new IllegalArgumentException(ExceptionLocalization.buildMessage( + "jpa_persistence_util_non_persistent_class", new Object[] {entity})); + } + return descriptor.getJavaClass(); + } + /** * Will return an instance of the Factory. Should only be called by * EclipseLink. @@ -624,25 +745,21 @@ public boolean isLoaded(Object entity) { return delegate.isLoaded(entity); } - // TODO-API-3.2 @Override public void load(Object entity, String attributeName) { delegate.load(entity, attributeName); } - // TODO-API-3.2 @Override public void load(E entity, Attribute attribute) { delegate.load(entity, attribute); } - // TODO-API-3.2 @Override public void load(Object entity) { delegate.load(entity); } - // TODO-API-3.2 @Override public boolean isInstance(Object entity, Class entityClass) { return delegate.isInstance(entity, entityClass); @@ -653,7 +770,6 @@ public String getName() { return delegate.getName(); } - // TODO-API-3.2 @Override public Class getClass(T entity) { return delegate.getClass(entity); diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/AbstractQueryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/AbstractQueryImpl.java index 3b613d0fc2c..eba69c1d082 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/AbstractQueryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/AbstractQueryImpl.java @@ -29,6 +29,7 @@ import jakarta.persistence.criteria.AbstractQuery; import jakarta.persistence.criteria.Expression; +import jakarta.persistence.criteria.ParameterExpression; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import jakarta.persistence.metamodel.EntityType; @@ -72,13 +73,28 @@ public AbstractQueryImpl(Metamodel metamodel, ResultType queryResult, CriteriaBu this.baseExpression = new ExpressionBuilder(); } + // Allows complete copy of CommonAbstractCriteriaImpl. Required for cast implementation and shall remain pkg private. + AbstractQueryImpl(Metamodel metamodel, Expression where, CriteriaBuilderImpl queryBuilder, + Class queryType, Set> parameters, + ResultType queryResult, boolean distinct, Predicate havingClause,List> groupBy, + Set> roots, org.eclipse.persistence.expressions.Expression baseExpression) { + super(metamodel, where, queryBuilder, queryType, parameters); + this.queryResult = queryResult; + this.distinct = distinct; + this.havingClause = havingClause; + this.groupBy = groupBy; + this.roots = roots; + this.baseExpression = baseExpression; + } + /** * Specify the expressions that are used to form groups over * the query results. * Replaces the previous specified grouping expressions, if any. * If no grouping expressions are specified, any previously * added grouping expressions are simply removed. - * @param grouping list of zero or more grouping expressions + * + * @param grouping list of zero or more grouping expressions * @return the modified query */ @Override diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java index 3492dfb1550..ab081351613 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java @@ -71,6 +71,16 @@ public CommonAbstractCriteriaImpl(Metamodel metamodel, CriteriaBuilderImpl query this.queryType = resultType; } + // Allows complete copy of CommonAbstractCriteriaImpl. Required for cast implementation and shall remain pkg private. + CommonAbstractCriteriaImpl(Metamodel metamodel, Expression where, CriteriaBuilderImpl queryBuilder, + Class queryType, Set> parameters) { + this.metamodel = metamodel; + this.where = where; + this.queryBuilder = queryBuilder; + this.queryType = queryType; + this.parameters = parameters; + } + /** * Return the predicate that corresponds to the where clause restriction(s). * diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java index 02e455c7016..462fe1e7e86 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java @@ -497,8 +497,19 @@ protected ReadAllQuery createCompoundQuery(boolean toReportQuery) { } if (this.queryResult.equals(ResultType.PARTIAL)) { + ReadAllQuery raq; // TODO: allow force of ReportQuery creation for usage in UNION - ReadAllQuery raq = new ReadAllQuery(this.queryType); + // TODO: In progress, code is not working and tested !!! + // See testUnionWithMultiselectEntityParametersInSelection TODO in UnionCriteriaQueryTest + if (toReportQuery) { + //raq = createReportQuery(this.queryType); + raq = createReportQueryWithItem(this.queryType); + } else { + raq = new ReadAllQuery(this.queryType); + } + // TODO: double check whether this may be avoided + raq.dontMaintainCache(); + // TODO: ROOTS? for (Selection selection : this.selection.getCompoundSelectionItems()) { raq.addPartialAttribute(((SelectionImpl) selection).currentNode); } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/SubQueryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/SubQueryImpl.java index b56910b883f..2762f62130a 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/SubQueryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/SubQueryImpl.java @@ -40,7 +40,6 @@ import jakarta.persistence.criteria.Subquery; import jakarta.persistence.metamodel.Metamodel; import jakarta.persistence.metamodel.Type.PersistenceType; - import org.eclipse.persistence.expressions.ExpressionBuilder; import org.eclipse.persistence.internal.expressions.ConstantExpression; import org.eclipse.persistence.internal.expressions.SubSelectExpression; @@ -93,13 +92,33 @@ public SubQueryImpl(Metamodel metamodel, Class result, CriteriaBuilderImpl qu this.parent = parent; } - /** - * Specify the item that is to be returned in the query result. - * Replaces the previously specified selection, if any. - * @param selection selection specifying the item that - * is to be returned in the query result - * @return the modified query - */ + // Allows complete copy of CommonAbstractCriteriaImpl. Required for cast implementation and shall remain private. + private SubQueryImpl(Metamodel metamodel, Expression where, CriteriaBuilderImpl queryBuilder, + Class queryType, Set> parameters, + ResultType queryResult, boolean distinct, Predicate havingClause,List> groupBy, + Set> roots, org.eclipse.persistence.expressions.Expression baseExpression, + SelectionImpl selection,SubSelectExpression currentNode, String alias, ReportQuery subQuery, + Set> correlatedJoins, CommonAbstractCriteria parent, Set processedJoins, + Set correlations) { + super(metamodel, where, queryBuilder, queryType, parameters, + queryResult, distinct, havingClause, groupBy, roots, baseExpression); + this.selection = selection; + this.currentNode = currentNode; + this.alias = alias; + this.subQuery = subQuery; + this.correlatedJoins = correlatedJoins; + this.parent = parent; + this.processedJoins = processedJoins; + this.correlations = correlations; + } + + /** + * Specify the item that is to be returned in the query result. + * Replaces the previously specified selection, if any. + * @param selection selection specifying the item that + * is to be returned in the query result + * @return the modified query + */ @Override public Subquery select(Expression selection) { findRootAndParameters(selection); @@ -436,6 +455,15 @@ public Expression as(Class type) { return (Expression) this; } + @Override + public Expression cast(Class aClass) { + // JPA spec: New instance with provided Java type + return new SubQueryImpl<>( + metamodel, where, queryBuilder, aClass, parameters, + queryResult, distinct, havingClause, groupBy, roots, baseExpression, + selection, currentNode, alias, subQuery, correlatedJoins, parent, processedJoins, correlations); + } + @Override public Predicate in(Object... values) { List> list = new ArrayList<>(); @@ -674,10 +702,4 @@ public DatabaseQuery getDatabaseQuery() { return this.subQuery; } - // TODO-API-3.2 - @Override - public Expression cast(Class aClass) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); - } - }