diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java index 4b670f01c9c..b81e6fa7322 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java @@ -933,7 +933,12 @@ public Object executeDatabaseQuery() throws DatabaseException { return getDescriptor().getInterfacePolicy().selectAllObjectsUsingMultipleTableSubclassRead(this); } - return buildObjects(getQueryMechanism().selectAllReportQueryRows()); + List rows = getQueryMechanism().selectAllReportQueryRows(); + if ((this.batchFetchPolicy != null) && this.batchFetchPolicy.isIN()) { + this.batchFetchPolicy.setDataResults(rows); + } + + return buildObjects((Vector) rows); } /** diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQueryResult.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQueryResult.java index 5cd63ce108c..0a35cb99782 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQueryResult.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQueryResult.java @@ -252,9 +252,26 @@ protected Object processItem(ReportQuery query, AbstractRecord row, Vector toMan AbstractRecord subRow = row; // Check if at the start of the row, then avoid building a subRow. if (itemIndex > 0) { - Vector trimedFields = new NonSynchronizedSubVector<>(row.getFields(), itemIndex, rowSize); - Vector trimedValues = new NonSynchronizedSubVector(row.getValues(), itemIndex, rowSize); - subRow = new DatabaseRecord(trimedFields, trimedValues); + BatchFetchPolicy batchFetchPolicy = query.getBatchFetchPolicy(); + if (batchFetchPolicy != null && batchFetchPolicy.isIN()) { + + List subRows = new ArrayList<>(toManyData.size()); + for (AbstractRecord parentRow : (Vector) toManyData) { + Vector trimedParentFields = new NonSynchronizedSubVector<>(parentRow.getFields(), itemIndex, rowSize); + Vector trimedParentValues = new NonSynchronizedSubVector<>(parentRow.getValues(), itemIndex, rowSize); + subRows.add(new DatabaseRecord(trimedParentFields, trimedParentValues)); + } + + for (DatabaseMapping subMapping : descriptor.getMappings()) { + batchFetchPolicy.setDataResults(subMapping, subRows); + } + + subRow = subRows.get(toManyData.indexOf(row)); + } else { + Vector trimedFields = new NonSynchronizedSubVector<>(row.getFields(), itemIndex, rowSize); + Vector trimedValues = new NonSynchronizedSubVector(row.getValues(), itemIndex, rowSize); + subRow = new DatabaseRecord(trimedFields, trimedValues); + } } if (mapping != null && mapping.isAggregateObjectMapping()){ value = ((AggregateObjectMapping)mapping).buildAggregateFromRow(subRow, null, null, joinManager, query, false, query.getSession(), true); diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/pom.xml b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/pom.xml new file mode 100644 index 00000000000..24189239f1e --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/pom.xml @@ -0,0 +1,82 @@ + + + + + org.eclipse.persistence.jpa.testapps + org.eclipse.persistence + 5.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + org.eclipse.persistence.jpa.testapps.batchfetch + + Test - batchfetch + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + get-test-classpath-to-properties + process-test-classes + + + + + + org.carlspring.maven + derby-maven-plugin + + + start-derby + process-test-classes + + + stop-derby + prepare-package + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + + + -javaagent:${org.eclipse.persistence:org.eclipse.persistence.jpa:jar} + + + + server-test + + + + **/EntityManagerImplTest + **/EntityManagerFactoryImplTest + + + + + + + + \ No newline at end of file diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/BatchFetchTableCreator.java b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/BatchFetchTableCreator.java new file mode 100644 index 00000000000..d9157684e47 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/BatchFetchTableCreator.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 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: + +package org.eclipse.persistence.testing.models.jpa.batchfetch; + +import org.eclipse.persistence.tools.schemaframework.FieldDefinition; +import org.eclipse.persistence.tools.schemaframework.TableCreator; +import org.eclipse.persistence.tools.schemaframework.TableDefinition; + +public class BatchFetchTableCreator extends TableCreator { + public BatchFetchTableCreator() { + setName("BatchFetchProject"); + + addTableDefinition(buildCompanyTable()); + addTableDefinition(buildEmployeeTable()); + addTableDefinition(buildRecordTable()); + } + + public TableDefinition buildRecordTable() { + TableDefinition table = new TableDefinition(); + table.setName("BATCH_IN_RECORD"); + + FieldDefinition fieldID = new FieldDefinition(); + fieldID.setName("ID"); + fieldID.setTypeName("NUMBER"); + fieldID.setSize(19); + fieldID.setSubSize(0); + fieldID.setIsPrimaryKey(true); + fieldID.setIsIdentity(true); + fieldID.setShouldAllowNull(false); + table.addField(fieldID); + + FieldDefinition fieldUSER = new FieldDefinition(); + fieldUSER.setName("EMPLOYEE_ID"); + fieldUSER.setTypeName("NUMBER"); + fieldUSER.setSize(19); + fieldUSER.setSubSize(0); + fieldUSER.setIsPrimaryKey(false); + fieldUSER.setIsIdentity(false); + fieldUSER.setShouldAllowNull(false); + fieldUSER.setForeignKeyFieldName("BATCH_IN_EMPLOYEE.ID"); + table.addField(fieldUSER); + + return table; + } + + public TableDefinition buildCompanyTable() { + TableDefinition table = new TableDefinition(); + table.setName("BATCH_IN_COMPANY"); + + FieldDefinition fieldID = new FieldDefinition(); + fieldID.setName("ID"); + fieldID.setTypeName("NUMBER"); + fieldID.setSize(19); + fieldID.setSubSize(0); + fieldID.setIsPrimaryKey(true); + fieldID.setIsIdentity(true); + fieldID.setShouldAllowNull(false); + table.addField(fieldID); + + return table; + } + + + public TableDefinition buildEmployeeTable() { + TableDefinition table = new TableDefinition(); + table.setName("BATCH_IN_EMPLOYEE"); + + FieldDefinition fieldID = new FieldDefinition(); + fieldID.setName("ID"); + fieldID.setTypeName("NUMBER"); + fieldID.setSize(19); + fieldID.setSubSize(0); + fieldID.setIsPrimaryKey(true); + fieldID.setIsIdentity(true); + fieldID.setShouldAllowNull(false); + table.addField(fieldID); + + FieldDefinition fieldCompany = new FieldDefinition(); + fieldCompany.setName("COMPANY_ID"); + fieldCompany.setTypeName("NUMBER"); + fieldCompany.setSize(19); + fieldCompany.setSubSize(0); + fieldCompany.setIsPrimaryKey(false); + fieldCompany.setIsIdentity(false); + fieldCompany.setShouldAllowNull(false); + fieldCompany.setForeignKeyFieldName("BATCH_IN_COMPANY.ID"); + table.addField(fieldCompany); + + return table; + + } + +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Company.java b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Company.java new file mode 100644 index 00000000000..b14f343f285 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Company.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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: + +package org.eclipse.persistence.testing.models.jpa.batchfetch; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import org.eclipse.persistence.annotations.BatchFetch; +import org.eclipse.persistence.annotations.BatchFetchType; + +import java.util.List; + +@Entity +@Table(name = "BATCH_IN_COMPANY") +public class Company { + @Id + private long id; + + public Company() { + } + + public Company(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Count.java b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Count.java new file mode 100644 index 00000000000..ed7bec32a3f --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Count.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 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: + +package org.eclipse.persistence.testing.models.jpa.batchfetch; + +public record Count(long value, Employee employee) {} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Employee.java b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Employee.java new file mode 100644 index 00000000000..ed412a475d2 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Employee.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 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: + +package org.eclipse.persistence.testing.models.jpa.batchfetch; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import org.eclipse.persistence.annotations.BatchFetch; +import org.eclipse.persistence.annotations.BatchFetchType; + +@Entity +@Table(name = "BATCH_IN_EMPLOYEE") +public class Employee { + @Id + private long id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "COMPANY_ID") + @BatchFetch(value = BatchFetchType.IN) + private Company company; + + public Employee() { + } + + public Employee(long id, Company company) { + this.id = id; + this.company = company; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Company getCompany() { + return company; + } + + public void setCompany(Company company) { + this.company = company; + } +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Record.java b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Record.java new file mode 100644 index 00000000000..de2c0952cda --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Record.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 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: + +package org.eclipse.persistence.testing.models.jpa.batchfetch; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import org.eclipse.persistence.annotations.BatchFetch; +import org.eclipse.persistence.annotations.BatchFetchType; + +@Entity +@Table(name = "BATCH_IN_RECORD") +public class Record { + @Id + private long id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "EMPLOYEE_ID") + @BatchFetch(value = BatchFetchType.IN) + private Employee employee; + + public Record() { + } + + public Record(long id, Employee employee) { + this.id = id; + this.employee = employee; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Employee getEmployee() { + return employee; + } + + public void setEmployee(Employee employee) { + this.employee = employee; + } +} diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/resources/META-INF/persistence.xml b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/resources/META-INF/persistence.xml new file mode 100644 index 00000000000..fd2de948d77 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,29 @@ + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.eclipse.persistence.testing.models.jpa.batchfetch.Company + org.eclipse.persistence.testing.models.jpa.batchfetch.Employee + org.eclipse.persistence.testing.models.jpa.batchfetch.Record + + + + + + + diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/test/java/org/eclipse/persistence/testing/tests/jpa/batchfetch/BatchFetchJUnitTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/test/java/org/eclipse/persistence/testing/tests/jpa/batchfetch/BatchFetchJUnitTest.java new file mode 100644 index 00000000000..9fa5638afec --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.batchfetch/src/test/java/org/eclipse/persistence/testing/tests/jpa/batchfetch/BatchFetchJUnitTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 1998, 2024 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: + +package org.eclipse.persistence.testing.tests.jpa.batchfetch; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import junit.framework.Test; +import junit.framework.TestSuite; +import org.eclipse.persistence.config.QueryHints; +import org.eclipse.persistence.testing.models.jpa.batchfetch.BatchFetchTableCreator; +import org.eclipse.persistence.testing.models.jpa.batchfetch.Company; +import org.eclipse.persistence.testing.models.jpa.batchfetch.Count; +import org.eclipse.persistence.testing.models.jpa.batchfetch.Employee; +import org.eclipse.persistence.testing.models.jpa.batchfetch.Record; +import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase; + +import java.util.List; +import java.util.Objects; + +public class BatchFetchJUnitTest extends JUnitTestCase { + + public BatchFetchJUnitTest() { + super(); + } + + public BatchFetchJUnitTest(String name) { + super(name); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.setName("BatchFetchJunitTest"); + suite.addTest(new BatchFetchJUnitTest("testSetup")); + suite.addTest(new BatchFetchJUnitTest("testSelectRoot")); + suite.addTest(new BatchFetchJUnitTest("testSelectNonRoot")); + suite.addTest(new BatchFetchJUnitTest("testSelectNonRootWithOffsetAndLimit")); + return suite; + } + + /** + * The setup is done as a test, both to record its failure, and to allow execution in the server. + */ + public void testSetup() { + new BatchFetchTableCreator().replaceTables(JUnitTestCase.getServerSession( + "batchfetch")); + EntityManager em = createEntityManager(); + createRecords(em); + } + + public void createRecords(EntityManager em){ + try { + beginTransaction(em); + Company c1 = new Company(1); + Company c2 = new Company(2); + em.persist(c1); + em.persist(c2); + + Employee u1 = new Employee(1, c1); + Employee u2 = new Employee(2, c1); + Employee u3 = new Employee(3, c2); + em.persist(u1); + em.persist(u2); + em.persist(u3); + + Record r1 = new Record(1, u1); + Record r2 = new Record(2, u2); + Record r3 = new Record(3, u3); + em.persist(r1); + em.persist(r2); + em.persist(r3); + + commitTransaction(em); + } catch (RuntimeException ex) { + if (isTransactionActive(em)) { + rollbackTransaction(em); + } + throw ex; + } finally { + closeEntityManager(em); + } + } + + public void testSelectRoot() { + EntityManager em = createEntityManager(); + em.getEntityManagerFactory().getCache().evictAll(); + + try { + TypedQuery q = em.createQuery("SELECT r FROM Record r", Record.class); + List result = q.getResultList(); + assertEquals("Not all rows are selected", 3, result.size()); + List employees = result.stream().map(Record::getEmployee).filter(Objects::nonNull).toList(); + assertEquals("Not all rows have employees", 3, employees.size()); + List companies = employees.stream().map(Employee::getCompany).filter(Objects::nonNull).toList(); + assertEquals("Not all employees have companies", 3, companies.size()); + } catch (RuntimeException e) { + closeEntityManager(em); + throw e; + } + } + + public void testSelectNonRoot() { + EntityManager em = createEntityManager(); + em.getEntityManagerFactory().getCache().evictAll(); + + try { + TypedQuery q = em.createQuery("SELECT r.employee FROM Record r", Employee.class); + List result = q.getResultList(); + assertEquals("Not all rows are selected", 3, result.size()); + List companies = result.stream().map(Employee::getCompany).filter(Objects::nonNull).toList(); + assertEquals("Not all employees have companies", 3, companies.size()); + } catch (RuntimeException e) { + closeEntityManager(em); + throw e; + } + } + + public void testSelectNonRootWithOffsetAndLimit() { + EntityManager em = createEntityManager(); + em.getEntityManagerFactory().getCache().evictAll(); + + try { + TypedQuery q = em.createQuery("SELECT new org.eclipse.persistence.testing.models.jpa.batchfetch.Count(count(r.employee), r.employee) FROM Record r group by r.employee", Count.class); + q.setHint(QueryHints.BATCH_SIZE, 1); + List result = q.getResultList(); + assertEquals("Not all rows are selected", 3, result.size()); + List employees = result.stream().map(Count::employee).filter(Objects::nonNull).toList(); + assertEquals("Not all counts have employees", 3, result.size()); + List companies = employees.stream().map(Employee::getCompany).filter(Objects::nonNull).toList(); + assertEquals("Not all employees have companies", 3, companies.size()); + } catch (RuntimeException e) { + closeEntityManager(em); + throw e; + } + } + + @Override + public String getPersistenceUnitName() { + return "batchfetch"; + } +} diff --git a/jpa/eclipselink.jpa.testapps/pom.xml b/jpa/eclipselink.jpa.testapps/pom.xml index 0fc9582dbd3..3ecf377b352 100644 --- a/jpa/eclipselink.jpa.testapps/pom.xml +++ b/jpa/eclipselink.jpa.testapps/pom.xml @@ -290,6 +290,7 @@ jpa.test.advanced.embeddable jpa.test.advanced.fetchgroup jpa.test.advanced.multitenant + jpa.test.batchfetch jpa.test.beanvalidation jpa.test.beanvalidation.dynamic jpa.test.cacheable