diff --git a/jpa/eclipselink.jpa.test/pom.xml b/jpa/eclipselink.jpa.test/pom.xml
index 6043311ac8e..51c1fe14795 100644
--- a/jpa/eclipselink.jpa.test/pom.xml
+++ b/jpa/eclipselink.jpa.test/pom.xml
@@ -1,7 +1,7 @@
+ eclipselink-batchfetch-model
+ eclipselink-batchfetch-model
+ batchfetch
+ org.eclipse.persistence.testing.tests.jpa.batchfetch.BatchFetchJUnitTest
+
+
server-test-jpa-beanvalidation
diff --git a/jpa/eclipselink.jpa.test/src/it/assembly/eclipselink-batchfetch-model.xml b/jpa/eclipselink.jpa.test/src/it/assembly/eclipselink-batchfetch-model.xml
new file mode 100644
index 00000000000..1b4bf604c5a
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/assembly/eclipselink-batchfetch-model.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ ${project.build.testOutputDirectory}
+ .
+
+ org/eclipse/persistence/testing/models/jpa/batchfetch/**
+
+
+
+
\ No newline at end of file
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/BatchFetchTableCreator.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/BatchFetchTableCreator.java
new file mode 100644
index 00000000000..d9157684e47
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/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.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Company.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Company.java
new file mode 100644
index 00000000000..b14f343f285
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/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.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Count.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Count.java
new file mode 100644
index 00000000000..9d825986381
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Count.java
@@ -0,0 +1,42 @@
+/*
+ * 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 class Count {
+
+ private long value;
+ private Employee emp;
+
+ public Count(long value, Employee emp) {
+ this.value = value;
+ this.emp = emp;
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+
+ public Employee getEmp() {
+ return emp;
+ }
+
+ public void setEmp(Employee emp) {
+ this.emp = emp;
+ }
+}
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Employee.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Employee.java
new file mode 100644
index 00000000000..ed412a475d2
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/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.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Record.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/models/jpa/batchfetch/Record.java
new file mode 100644
index 00000000000..de2c0952cda
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/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.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/FullRegressionTestSuite.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/FullRegressionTestSuite.java
index 21319dad7af..824fc6cc17a 100644
--- a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/FullRegressionTestSuite.java
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/FullRegressionTestSuite.java
@@ -1,6 +1,6 @@
/*
- * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
+ * Copyright (c) 1998, 2024 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2024 IBM Corporation. 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
@@ -60,6 +60,7 @@
import org.eclipse.persistence.testing.tests.jpa.advanced.fetchgroup.AdvancedFetchGroupJunitTest;
import org.eclipse.persistence.testing.tests.jpa.advanced.multitenant.AdvancedMultiTenantJunitTest;
import org.eclipse.persistence.testing.tests.jpa.advanced.multitenant.AdvancedMultiTenantSchemaJunitTest;
+import org.eclipse.persistence.testing.tests.jpa.batchfetch.BatchFetchJUnitTest;
import org.eclipse.persistence.testing.tests.jpa.cacheable.CacheableModelJunitTest;
import org.eclipse.persistence.testing.tests.jpa.cacheable.CacheableModelJunitTestEnableSelective;
import org.eclipse.persistence.testing.tests.jpa.cascadedeletes.CascadeDeletesJUnitTestSuite;
@@ -371,6 +372,9 @@ public static TestSuite suite4() {
// Persistence Unit Processor tests.
fullSuite.addTest(PersistenceUnitProcessorTest.suite());
+ // Batchfetch tests
+ fullSuite.addTest(BatchFetchJUnitTest.suite());
+
return fullSuite;
}
}
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/batchfetch/BatchFetchJUnitTest.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/batchfetch/BatchFetchJUnitTest.java
new file mode 100644
index 00000000000..e3e02e324cf
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/batchfetch/BatchFetchJUnitTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.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.junit.JUnitTestCase;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+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).collect(Collectors.toList());
+ assertEquals("Not all rows have employees", 3, employees.size());
+ List companies = employees.stream().map(Employee::getCompany).filter(Objects::nonNull).collect(Collectors.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).collect(Collectors.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::getEmp).filter(Objects::nonNull).collect(Collectors.toList());
+ assertEquals("Not all counts have employees", 3, result.size());
+ List companies = employees.stream().map(Employee::getCompany).filter(Objects::nonNull).collect(Collectors.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.test/src/it/resources/eclipselink-batchfetch-model/persistence.xml b/jpa/eclipselink.jpa.test/src/it/resources/eclipselink-batchfetch-model/persistence.xml
new file mode 100644
index 00000000000..126ea737a61
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/resources/eclipselink-batchfetch-model/persistence.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ 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.test/src/it/resources/eclipselink-batchfetch-model/server/persistence.xml b/jpa/eclipselink.jpa.test/src/it/resources/eclipselink-batchfetch-model/server/persistence.xml
new file mode 100644
index 00000000000..517f3648de4
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/it/resources/eclipselink-batchfetch-model/server/persistence.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ org.eclipse.persistence.jpa.PersistenceProvider
+ <@datasource-type@>@data-source-name@@datasource-type@>
+ org.eclipse.persistence.testing.models.jpa.batchfetch.Company
+ org.eclipse.persistence.testing.models.jpa.batchfetch.Employee
+ org.eclipse.persistence.testing.models.jpa.batchfetch.Record
+
+
+
+
+
+
+
+