This is an automated email from the ASF dual-hosted git repository.
MartijnVisser pushed a commit to branch v3.4
in repository https://gitbox.apache.org/repos/asf/flink-connector-jdbc.git
The following commit(s) were added to refs/heads/v3.4 by this push:
new 93debc91 [FLINK-39206][jdbc] Give each split reader its own connection
provider
93debc91 is described below
commit 93debc91b1d6471dfd5c9022e518ef81729ab3e1
Author: Martijn Visser <[email protected]>
AuthorDate: Mon Sep 21 09:18:30 2026 +0200
[FLINK-39206][jdbc] Give each split reader its own connection provider
JdbcSource handed the same JdbcConnectionProvider to every split reader it
created, while flink-connector-base builds a split reader per split fetcher
and
closes the reader of a finished split while the reader of the next split is
already reading. A provider holds a single connection and is not thread
safe,
so the finished reader closed the running reader's result set and the job
failed instead of finishing.
Copy the provider per split reader, the way other connectors give each split
reader its own client. A reader now owns its copy, so it closes the result
set,
the connection and the provider together, and the interface says that it is
copied.
The deprecated org.apache.flink.connector.jdbc.source.JdbcSource builds the
same
split readers, so it copies the provider too.
Generated-by: Claude Code (Opus 5)
---
.../jdbc/core/datastream/source/JdbcSource.java | 35 +++++--
.../source/reader/JdbcSourceSplitReader.java | 14 ++-
.../connections/JdbcConnectionProvider.java | 9 +-
.../core/datastream/source/JdbcSourceTest.java | 111 +++++++++++++++++++++
.../flink/connector/jdbc/source/JdbcSource.java | 5 +-
5 files changed, 157 insertions(+), 17 deletions(-)
diff --git
a/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSource.java
b/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSource.java
index 8efb732e..0822ecc6 100644
---
a/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSource.java
+++
b/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSource.java
@@ -30,6 +30,7 @@ import
org.apache.flink.api.connector.source.SplitEnumeratorContext;
import org.apache.flink.api.java.typeutils.ResultTypeQueryable;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.connector.base.DeliveryGuarantee;
+import org.apache.flink.connector.base.source.reader.splitreader.SplitReader;
import
org.apache.flink.connector.jdbc.core.datastream.source.config.ContinuousUnBoundingSettings;
import
org.apache.flink.connector.jdbc.core.datastream.source.enumerator.JdbcSourceEnumStateSerializer;
import
org.apache.flink.connector.jdbc.core.datastream.source.enumerator.JdbcSourceEnumerator;
@@ -37,11 +38,13 @@ import
org.apache.flink.connector.jdbc.core.datastream.source.enumerator.JdbcSou
import
org.apache.flink.connector.jdbc.core.datastream.source.enumerator.JdbcSqlSplitEnumeratorBase;
import
org.apache.flink.connector.jdbc.core.datastream.source.reader.JdbcSourceReader;
import
org.apache.flink.connector.jdbc.core.datastream.source.reader.JdbcSourceSplitReader;
+import
org.apache.flink.connector.jdbc.core.datastream.source.reader.RecordAndOffset;
import
org.apache.flink.connector.jdbc.core.datastream.source.reader.extractor.ResultExtractor;
import
org.apache.flink.connector.jdbc.core.datastream.source.split.JdbcSourceSplit;
import
org.apache.flink.connector.jdbc.core.datastream.source.split.JdbcSourceSplitSerializer;
import
org.apache.flink.connector.jdbc.datasource.connections.JdbcConnectionProvider;
import org.apache.flink.core.io.SimpleVersionedSerializer;
+import org.apache.flink.util.InstantiationUtil;
import org.apache.flink.util.Preconditions;
import javax.annotation.Nullable;
@@ -49,6 +52,7 @@ import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Supplier;
/** JDBC source. */
@PublicEvolving
@@ -98,16 +102,27 @@ public class JdbcSource<OUT>
public SourceReader<OUT, JdbcSourceSplit> createReader(SourceReaderContext
readerContext)
throws Exception {
return new JdbcSourceReader<>(
- () ->
- new JdbcSourceSplitReader<>(
- readerContext,
- configuration,
- typeInformation,
- connectionProvider,
- deliveryGuarantee,
- resultExtractor),
- configuration,
- readerContext);
+ splitReaderSupplier(readerContext), configuration,
readerContext);
+ }
+
+ /**
+ * Supplies the split readers of one {@link JdbcSourceReader}. Every
reader gets its own
+ * connection provider: a provider holds a single connection and is not
thread safe, while
+ * flink-connector-base builds a split reader per split fetcher and closes
the reader of a
+ * finished split while the reader of the next split is already reading.
Sharing one provider
+ * lets the first close the connection out from under the second.
+ */
+ @VisibleForTesting
+ Supplier<SplitReader<RecordAndOffset<OUT>, JdbcSourceSplit>>
splitReaderSupplier(
+ SourceReaderContext readerContext) {
+ return () ->
+ new JdbcSourceSplitReader<>(
+ readerContext,
+ configuration,
+ typeInformation,
+ InstantiationUtil.cloneUnchecked(connectionProvider),
+ deliveryGuarantee,
+ resultExtractor);
}
@Override
diff --git
a/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/reader/JdbcSourceSplitReader.java
b/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/reader/JdbcSourceSplitReader.java
index e9b1ca5d..2fa7733a 100644
---
a/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/reader/JdbcSourceSplitReader.java
+++
b/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/core/datastream/source/reader/JdbcSourceSplitReader.java
@@ -32,6 +32,7 @@ import
org.apache.flink.connector.base.source.reader.splitreader.SplitsChange;
import
org.apache.flink.connector.jdbc.core.datastream.source.reader.extractor.ResultExtractor;
import
org.apache.flink.connector.jdbc.core.datastream.source.split.JdbcSourceSplit;
import
org.apache.flink.connector.jdbc.datasource.connections.JdbcConnectionProvider;
+import org.apache.flink.util.IOUtils;
import org.apache.flink.util.Preconditions;
import org.slf4j.Logger;
@@ -221,12 +222,15 @@ public class JdbcSourceSplitReader<T>
@Override
public void close() throws Exception {
- closeResultSetAndStatement();
- if (connection != null && !connection.isClosed()) {
- connection.close();
+ try {
+ // The provider is this reader's own copy, so it is closed here
too: that runs the
+ // teardown of a user-supplied provider, and covers the connection
if the provider
+ // hands out one it does not track itself.
+ IOUtils.closeAll(this::closeResultSetAndStatement, connection,
connectionProvider);
+ } finally {
+ connection = null;
+ currentSplit = null;
}
- connection = null;
- currentSplit = null;
}
@Override
diff --git
a/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/datasource/connections/JdbcConnectionProvider.java
b/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/datasource/connections/JdbcConnectionProvider.java
index 81c6bccb..b43b9e3e 100644
---
a/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/datasource/connections/JdbcConnectionProvider.java
+++
b/flink-connector-jdbc-core/src/main/java/org/apache/flink/connector/jdbc/datasource/connections/JdbcConnectionProvider.java
@@ -27,7 +27,14 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
-/** JDBC connection provider. */
+/**
+ * Provides the connection a JDBC source or sink works on.
+ *
+ * <p>A source copies the provider once per split reader, by Java
serialization, so an
+ * implementation that wraps a pool or caches credentials gets one independent
copy per reader
+ * rather than one per subtask. Keep anything that must not be duplicated out
of the serialized
+ * state.
+ */
@PublicEvolving
public interface JdbcConnectionProvider extends Serializable, AutoCloseable {
/**
diff --git
a/flink-connector-jdbc-core/src/test/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSourceTest.java
b/flink-connector-jdbc-core/src/test/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSourceTest.java
new file mode 100644
index 00000000..d33faa4d
--- /dev/null
+++
b/flink-connector-jdbc-core/src/test/java/org/apache/flink/connector/jdbc/core/datastream/source/JdbcSourceTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.connector.jdbc.core.datastream.source;
+
+import org.apache.flink.api.common.typeinfo.TypeInformation;
+import org.apache.flink.connector.base.source.reader.RecordsWithSplitIds;
+import org.apache.flink.connector.base.source.reader.splitreader.SplitReader;
+import
org.apache.flink.connector.base.source.reader.splitreader.SplitsAddition;
+import org.apache.flink.connector.jdbc.JdbcDataTestBase;
+import
org.apache.flink.connector.jdbc.core.datastream.source.reader.JdbcSourceSplitReader;
+import
org.apache.flink.connector.jdbc.core.datastream.source.reader.RecordAndOffset;
+import
org.apache.flink.connector.jdbc.core.datastream.source.split.JdbcSourceSplit;
+import org.apache.flink.connector.testutils.source.reader.TestingReaderContext;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.apache.flink.connector.jdbc.JdbcTestFixture.INPUT_TABLE;
+import static org.apache.flink.connector.jdbc.JdbcTestFixture.TEST_DATA;
+import static org.apache.flink.connector.jdbc.JdbcTestFixture.TestEntry;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test for {@link JdbcSource}. */
+class JdbcSourceTest extends JdbcDataTestBase {
+
+ private static final String SQL = "SELECT id, title, author, price, qty
FROM " + INPUT_TABLE;
+
+ @Test
+ void testSplitReadersDoNotShareAConnection() throws Exception {
+ // flink-connector-base builds a split reader per split fetcher and
closes the reader of a
+ // finished split while the reader of the next split is already
reading. A connection
+ // provider holds a single connection and is not thread safe, so every
split reader needs
+ // its own: sharing one lets the finished reader close the running
reader's result set.
+ JdbcSource<TestEntry> source =
+ JdbcSource.<TestEntry>builder()
+
.setTypeInformation(TypeInformation.of(TestEntry.class))
+ .setSql(SQL)
+ .setDBUrl(getMetadata().getJdbcUrl())
+ .setDriverName(getMetadata().getDriverClass())
+ .setResultExtractor(extractor)
+ // One record per fetch, so the running reader still
has an open result set
+ // when the finished one is closed.
+ .setSplitReaderFetchBatchSize(1)
+ .build();
+
+ Supplier<SplitReader<RecordAndOffset<TestEntry>, JdbcSourceSplit>>
supplier =
+ source.splitReaderSupplier(new TestingReaderContext());
+
+ JdbcSourceSplitReader<TestEntry> finishedSplitReader =
newSplitReader(supplier, "1");
+ JdbcSourceSplitReader<TestEntry> runningSplitReader =
newSplitReader(supplier, "2");
+
+ // The first fetch on each opens its result set, which is what
establishes its connection.
+ drain(finishedSplitReader.fetch());
+ List<TestEntry> records = new
ArrayList<>(drain(runningSplitReader.fetch()));
+
+ finishedSplitReader.close();
+
+ // The finished reader is gone; the running one must still read its
split to the end.
+ // One record per fetch, so the remaining count is exactly the number
of fetches left.
+ for (int i = records.size(); i < TEST_DATA.length; i++) {
+ records.addAll(drain(runningSplitReader.fetch()));
+ }
+ runningSplitReader.close();
+ assertThat(records).containsExactlyInAnyOrder(TEST_DATA);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static JdbcSourceSplitReader<TestEntry> newSplitReader(
+ Supplier<SplitReader<RecordAndOffset<TestEntry>, JdbcSourceSplit>>
supplier,
+ String splitId) {
+ JdbcSourceSplitReader<TestEntry> splitReader =
+ (JdbcSourceSplitReader<TestEntry>) supplier.get();
+ splitReader.handleSplitsChanges(
+ new SplitsAddition<>(
+ Collections.singletonList(new JdbcSourceSplit(splitId,
SQL, null, null))));
+ return splitReader;
+ }
+
+ private static List<TestEntry> drain(
+ RecordsWithSplitIds<RecordAndOffset<TestEntry>> fetchedRecords) {
+ List<TestEntry> records = new ArrayList<>();
+ while (fetchedRecords.nextSplit() != null) {
+ RecordAndOffset<TestEntry> recordAndOffset =
fetchedRecords.nextRecordFromSplit();
+ while (recordAndOffset != null) {
+ records.add(recordAndOffset.getRecord());
+ recordAndOffset = fetchedRecords.nextRecordFromSplit();
+ }
+ }
+ return records;
+ }
+}
diff --git
a/flink-connector-jdbc/src/main/java/org/apache/flink/connector/jdbc/source/JdbcSource.java
b/flink-connector-jdbc/src/main/java/org/apache/flink/connector/jdbc/source/JdbcSource.java
index cee58e75..6cb936a8 100644
---
a/flink-connector-jdbc/src/main/java/org/apache/flink/connector/jdbc/source/JdbcSource.java
+++
b/flink-connector-jdbc/src/main/java/org/apache/flink/connector/jdbc/source/JdbcSource.java
@@ -42,6 +42,7 @@ import
org.apache.flink.connector.jdbc.core.datastream.source.split.JdbcSourceSp
import
org.apache.flink.connector.jdbc.datasource.connections.JdbcConnectionProvider;
import org.apache.flink.connector.jdbc.utils.ContinuousUnBoundingSettings;
import org.apache.flink.core.io.SimpleVersionedSerializer;
+import org.apache.flink.util.InstantiationUtil;
import org.apache.flink.util.Preconditions;
import javax.annotation.Nullable;
@@ -108,7 +109,9 @@ public class JdbcSource<OUT>
readerContext,
configuration,
typeInformation,
- connectionProvider,
+ // A provider holds a single connection and is
not thread safe, so
+ // every split reader gets its own copy, as in
the core JdbcSource.
+
InstantiationUtil.cloneUnchecked(connectionProvider),
deliveryGuarantee,
resultExtractor),
configuration,