BatchFetchType.IN causes IndexOutOfBoundsException in ForeignReferenceMapping.extractResultsFromBatchQuery (#1426)
Fixes #1148 (bugfix + unit test)
Signed-off-by: Radek Felcman <radek.felcman@oracle.com>
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java
index 28525a6..7c91501 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/ForeignReferenceMapping.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
@@ -576,6 +576,9 @@
// If only fetching a page, need to make sure the row we want is in the page.
startIndex = parentRows.indexOf(sourceRow);
}
+ if (startIndex == -1) {
+ return null;
+ }
List foreignKeyValues = new ArrayList(size);
Set foreignKeys = new HashSet(size);
int index = 0;
diff --git a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java
index 0a2c9e9..ff3c154 100644
--- a/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java
+++ b/jpa/eclipselink.jpa.test/src/it/java/org/eclipse/persistence/testing/tests/jpa/advanced/AdvancedJPAJunitTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
@@ -50,6 +50,7 @@
import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.Query;
+import jakarta.persistence.TypedQuery;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.Attribute.PersistentAttributeType;
import jakarta.persistence.metamodel.Bindable.BindableType;
@@ -282,6 +283,7 @@
suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeInReverseIteration"));
suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeInCustomIteration"));
suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeInRandomIteration"));
+ suite.addTest(new AdvancedJPAJunitTest("testEmployeeToProjectWithBatchFetchTypeWithSmallFetchSize"));
if (!isJPA10()) {
// These tests use JPA 2.0 entity manager API
@@ -3736,6 +3738,72 @@
}
/**
+ * Bug 1148 (GitHub)
+ * Test batch fetch with small fetch size
+ */
+ public void testEmployeeToProjectWithBatchFetchTypeWithSmallFetchSize() {
+ final String lastName = "testRandomEmployeeToProject";
+
+ // Set up
+ Set<Employee> employeesToRemove = new HashSet<>();
+ Set<Integer> employeeIds = new HashSet<>();
+ EntityManager em = createEntityManager();
+ for (int i = 0; i < 100; i++) {
+ beginTransaction(em);
+ Employee employee = new Employee();
+ employee.setLastName(lastName);
+ employeesToRemove.add(employee);
+ em.persist(employee);
+ employeeIds.add(employee.getId());
+ for (int j = 0; j < 20; j++) {
+ Project project = new Project();
+ employee.addProject(project);
+ em.persist(project);
+ }
+ commitTransaction(em);
+ }
+
+ JpaEntityManager jpaEntityManager = (JpaEntityManager) em.getDelegate();
+ jpaEntityManager.getUnitOfWork().getIdentityMapAccessor().initializeAllIdentityMaps();
+ try {
+ String jpql = "SELECT employee FROM Employee employee WHERE employee.id IN :ids";
+ TypedQuery<Employee> query = em.createQuery(jpql, Employee.class)
+ .setParameter("ids", employeeIds)
+ .setHint(QueryHints.BATCH_TYPE, BatchFetchType.IN)
+ .setHint(QueryHints.BATCH, "employee.projects.properties")
+ .setHint(QueryHints.BATCH_SIZE, 3);
+ List<Employee> employees = query.getResultList();
+
+ // Trigger the bug
+ Collections.shuffle(employees);
+
+ int count = 0;
+ try {
+ for (Employee employee : employees) {
+ for (Project project : employee.getProjects()) {
+ count++;
+ }
+ }
+ Assert.assertEquals("Project objects received are not as many as expected", 2000, count);
+ } catch (ArrayIndexOutOfBoundsException x) {
+ Assert.fail(Helper.printStackTraceToString(x));
+ }
+ } finally {
+ // Clean up
+ beginTransaction(em);
+ for (Employee employee : employeesToRemove) {
+ employee = em.merge(employee);
+ for (Project project : employee.getProjects()) {
+ em.remove(em.merge(project));
+ }
+ em.remove(employee);
+ }
+ commitTransaction(em);
+ closeEntityManager(em);
+ }
+ }
+
+ /**
* Bug 502085 - @OneToMany with @OrderColumn contains a null element after specific scenario
*
*/