diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ParameterExpression.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ParameterExpression.java index 005f16f2f0b..d5008ef3d1f 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ParameterExpression.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/expressions/ParameterExpression.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -239,7 +239,11 @@ public Object getValue(AbstractRecord translationRow, DatabaseQuery query, Abstr ClassDescriptor descriptor = session.getDescriptor(value); //Bug4924639 Aggregate descriptors have to be acquired from their mapping as they are cloned and initialized by each mapping if (descriptor != null && descriptor.isAggregateDescriptor() && ((ParameterExpression)getBaseExpression()).getLocalBase().isObjectExpression()) { - descriptor = ((ObjectExpression)((ParameterExpression)getBaseExpression()).getLocalBase()).getDescriptor(); + if (getLocalBase() != null && getLocalBase().isQueryKeyExpression()) { + descriptor = ((QueryKeyExpression)getLocalBase()).getMapping().getDescriptor(); + } else { + descriptor = ((ObjectExpression) ((ParameterExpression) getBaseExpression()).getLocalBase()).getDescriptor(); + } } if (descriptor == null) { diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/ExpressionQueryMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/ExpressionQueryMechanism.java index 2cdfaa51adb..076a7ee5d48 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/ExpressionQueryMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/ExpressionQueryMechanism.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 1998, 2024 IBM and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 IBM 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 @@ -57,6 +57,7 @@ import org.eclipse.persistence.internal.helper.InvalidObject; import org.eclipse.persistence.internal.helper.NonSynchronizedVector; import org.eclipse.persistence.internal.identitymaps.CacheKey; +import org.eclipse.persistence.internal.oxm.mappings.Mapping; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; @@ -2030,6 +2031,31 @@ public void prepareUpdateAll() { } baseExpressions.add(new FieldExpression(fields.get(i), ((QueryKeyExpression)baseExpression).getBaseExpression())); } + } else if (mapping != null && mapping.isAggregateMapping() && isValueObjectEmbeddableAndParameter(valueObject)) { + fields = mapping.getFields(); + int fieldsSize = fields.size(); + values = new ArrayList<>(fieldsSize); + baseExpressions = new ArrayList<>(fieldsSize); + for(DatabaseMapping databaseMapping: mapping.getReferenceDescriptor().getMappings()) { + String aName = databaseMapping.getAttributeName(); + Expression attributeBaseExpression = baseExpression.get(aName); + baseExpressions.add(attributeBaseExpression); + ParameterExpression exp = (ParameterExpression) ((ParameterExpression)valueObject).clone().getField(new DatabaseField(aName)); + builder.setSession(getSession()); + if (baseExpression.isQueryKeyExpression()) { + //It will set mapping if it was not initialized before + ((QueryKeyExpression)baseExpression).getMapping(); + } + if (attributeBaseExpression.isQueryKeyExpression()) { + //It will set mapping if it was not initialized before + ((QueryKeyExpression)attributeBaseExpression).getMapping(); + } + + exp.setLocalBase(attributeBaseExpression); + exp.getBaseExpression().setLocalBase(((ParameterExpression)valueObject).getLocalBase()); + + values.add(exp); + } } else { fields = new ArrayList<>(1); fields.add(field); @@ -2921,4 +2947,14 @@ protected ClassDescriptor getHighestDescriptorMappingTable(DatabaseTable table) } return desc; } + + private boolean isValueObjectEmbeddableAndParameter(Object valueObject) { + if (valueObject instanceof ParameterExpression) { + Class type = (Class) ((ParameterExpression)valueObject).getType(); + if (getSession().getDescriptor(type) != null) { + return true; + } + } + return false; + } } diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLModifyTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLModifyTest.java index c1ddf4fe844..a13d364efe0 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLModifyTest.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.jpql/src/test/java/org/eclipse/persistence/testing/tests/jpa/jpql/advanced/JUnitJPQLModifyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025 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 @@ -25,9 +25,12 @@ 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.EmploymentPeriod; import org.eclipse.persistence.testing.tests.jpa.jpql.JUnitDomainObjectComparer; +import java.time.LocalDate; import java.util.Calendar; +import java.util.List; /** *

@@ -99,6 +102,8 @@ public static Test suite() suite.addTest(new JUnitJPQLModifyTest("simpleUpdate")); suite.addTest(new JUnitJPQLModifyTest("updateWithSubquery")); suite.addTest(new JUnitJPQLModifyTest("updateEmbedded")); + suite.addTest(new JUnitJPQLModifyTest("updateEmbeddedObjectWithValue")); + suite.addTest(new JUnitJPQLModifyTest("updateEmbeddedObjectWithNull")); suite.addTest(new JUnitJPQLModifyTest("updateEmbeddedFieldTest")); suite.addTest(new JUnitJPQLModifyTest("updateUnqualifiedAttributeInSet")); suite.addTest(new JUnitJPQLModifyTest("updateUnqualifiedAttributeInWhere")); @@ -219,6 +224,78 @@ public void updateEmbedded() } } + public void updateEmbeddedObjectWithValue() + { + if ((getPersistenceUnitServerSession()).getPlatform().isSymfoware()) { + getPersistenceUnitServerSession().logMessage("Test updateEmbedded skipped for this platform, " + + "Symfoware doesn't support UpdateAll/DeleteAll on multi-table objects (see rfe 298193)."); + return; + } + EntityManager em = createEntityManager(); + + long nrOfEmps = executeJPQLReturningInt( + em, "SELECT COUNT(e) FROM Employee e"); + + // test query + EmploymentPeriod employmentPeriod = new EmploymentPeriod(java.sql.Date.valueOf(LocalDate.of(2020, 1, 1)), java.sql.Date.valueOf(LocalDate.of(2025, 12, 31))); + String update = "UPDATE Employee e SET e.period = ?1"; + beginTransaction(em); + try { + Query updateQuery = em.createQuery(update); + updateQuery.setParameter(1, employmentPeriod); + int updated = updateQuery.executeUpdate(); + assertEquals("updateEmbedded: wrong number of updated instances", + nrOfEmps, updated); + commitTransaction(em); + + // check database changes + Query selectQuery = em.createQuery("SELECT COUNT(e) FROM Employee e WHERE e.period.startDate = ?1", Long.class); + selectQuery.setParameter(1, employmentPeriod.getStartDate()); + long nr = (long) selectQuery.getSingleResult(); + assertEquals("updateEmbedded: unexpected number of changed values in the database", + nrOfEmps, nr); + } finally { + if (isTransactionActive(em)){ + rollbackTransaction(em); + } + } + } + + public void updateEmbeddedObjectWithNull() + { + if ((getPersistenceUnitServerSession()).getPlatform().isSymfoware()) { + getPersistenceUnitServerSession().logMessage("Test updateEmbedded skipped for this platform, " + + "Symfoware doesn't support UpdateAll/DeleteAll on multi-table objects (see rfe 298193)."); + return; + } + EntityManager em = createEntityManager(); + + int nrOfEmps = executeJPQLReturningInt( + em, "SELECT COUNT(e) FROM Employee e"); + + // test query + String update = "UPDATE Employee e SET e.period = ?1"; + beginTransaction(em); + try { + Query q = em.createQuery(update); + q.setParameter(1, null); + int updated = q.executeUpdate(); + assertEquals("updateEmbedded: wrong number of updated instances", + nrOfEmps, updated); + commitTransaction(em); + + // check database changes + int nr = executeJPQLReturningInt( + em, "SELECT COUNT(e) FROM Employee e WHERE e.period.startDate IS NULL"); + assertEquals("updateEmbedded: unexpected number of changed values in the database", + nrOfEmps, nr); + } finally { + if (isTransactionActive(em)){ + rollbackTransaction(em); + } + } + } + public void updateEmbeddedFieldTest() { if ((getPersistenceUnitServerSession()).getPlatform().isSymfoware()) {