From bc03f8d516613da0d09c3ee7171fb3228b3c351d Mon Sep 17 00:00:00 2001 From: "robin.bygrave" Date: Tue, 2 Jun 2026 14:42:44 +1200 Subject: [PATCH] open telemetry: Fix for IndexOutOfBoundsException with Batched PreparedStatements and open telemetry Caused by: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0 at jdk.internal.util.Preconditions.outOfBounds(Unknown Source) at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Unknown Source) at jdk.internal.util.Preconditions.checkIndex(Unknown Source) at java.util.Objects.checkIndex(Unknown Source) at java.util.ArrayList.get(Unknown Source) at io.ebeaninternal.server.persist.BatchedPstmt.profile(BatchedPstmt.java:132) --- .../server/persist/BatchedPstmt.java | 9 ++-- .../server/persist/BatchedPstmtTest.java | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 ebean-core/src/test/java/io/ebeaninternal/server/persist/BatchedPstmtTest.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java index e39f79629d..2c45fa374b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java @@ -116,9 +116,12 @@ public void executeBatch(boolean getGeneratedKeys) throws SQLException { getGeneratedKeys(); } postExecute(); - addTimingMetrics(); - list.clear(); - transaction.profileEvent(this); + try { + addTimingMetrics(); + transaction.profileEvent(this); + } finally { + list.clear(); + } } private void addTimingMetrics() { diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/persist/BatchedPstmtTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/persist/BatchedPstmtTest.java new file mode 100644 index 0000000000..97dd99828c --- /dev/null +++ b/ebean-core/src/test/java/io/ebeaninternal/server/persist/BatchedPstmtTest.java @@ -0,0 +1,46 @@ +package io.ebeaninternal.server.persist; + +import io.ebeaninternal.api.SpiProfileTransactionEvent; +import io.ebeaninternal.api.SpiTransaction; +import org.junit.jupiter.api.Test; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class BatchedPstmtTest { + + @Test + void executeBatch_profilesBeforeClearingBatch() throws SQLException { + var pstmt = mock(PreparedStatement.class); + var transaction = mock(SpiTransaction.class); + var postExecute = mock(BatchPostExecute.class); + + when(pstmt.executeBatch()).thenReturn(new int[]{1}); + when(transaction.profileOffset()).thenReturn(42L); + doAnswer(invocation -> { + var event = invocation.getArgument(0, SpiProfileTransactionEvent.class); + event.profile(); + return null; + }).when(transaction).profileEvent(any()); + + var batched = new BatchedPstmt(pstmt, false, "insert into foo values (?)", transaction); + batched.add(postExecute); + + batched.executeBatch(false); + + verify(postExecute).checkRowCount(1); + verify(postExecute).postExecute(); + verify(postExecute).addTimingBatch(anyLong(), eq(1)); + verify(postExecute).profile(42L, 1); + assertThat(batched.isEmpty()).isTrue(); + } +}