This is an automated email from the ASF dual-hosted git repository.

ppa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 219b0f5ee53 IGNITE-27134 Jdbc. Added backgroundReconnectInterval 
property to connection configuration (#7093)
219b0f5ee53 is described below

commit 219b0f5ee534957070ed231d5faea2750518bbba
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Mon Dec 1 16:54:57 2025 +0300

    IGNITE-27134 Jdbc. Added backgroundReconnectInterval property to connection 
configuration (#7093)
---
 .../developers-guide/clients/jdbc-driver.adoc      |   1 +
 modules/jdbc/build.gradle                          |   1 +
 .../ignite/jdbc/ItJdbcConnectionFailoverTest.java  | 104 ++++++++++++++++++++-
 .../ignite/jdbc/ItJdbcConnectionSelfTest.java      |  50 ++++++++++
 .../ignite/internal/jdbc/ConnectionProperties.java |   9 ++
 .../internal/jdbc/ConnectionPropertiesImpl.java    |  15 ++-
 .../ignite/internal/jdbc/JdbcConnection.java       |   3 +-
 .../org/apache/ignite/jdbc/IgniteJdbcDriver.java   |  12 ++-
 .../client/ItThinClientChannelValidatorTest.java   |   3 +-
 9 files changed, 188 insertions(+), 10 deletions(-)

diff --git a/docs/_docs/developers-guide/clients/jdbc-driver.adoc 
b/docs/_docs/developers-guide/clients/jdbc-driver.adoc
index ad6d88092e0..19690ee2be6 100644
--- a/docs/_docs/developers-guide/clients/jdbc-driver.adoc
+++ b/docs/_docs/developers-guide/clients/jdbc-driver.adoc
@@ -72,6 +72,7 @@ 
jdbc:ignite:thin://host[:port][,host[:port][/schema][[?parameter1=value1][;param
 ** `partitionAwarenessMetadataCacheSize` - Size of cache to store partition 
awareness metadata of queries, in number of entries. Default value: `1024`.
 ** `queryTimeout` - Number of seconds the driver will wait for a `Statement` 
object to execute. 0 means there is no limit. Default value: `0`.
 ** `connectionTimeout` - Number of milliseconds JDBC client will wait for 
server to respond. 0 means there is no limit. Default value: `0`.
+** `backgroundReconnectIntervalMillis` - Specifies the wait time in 
milliseconds between runs of the background reconnection procedure. This 
procedure is used to restore old and establish new connections to cluster 
nodes. The value `0` can be used to disable background reconnection. Default 
value is `30000` (30 seconds).
 ** `username` - username for basic authentication to the cluster.
 ** `password` - user password for basic authentication to the cluster.
 ** `sslEnabled` - Determines if SSL is enabled. Possible values: `true`, 
`false`. Default value: `false`
diff --git a/modules/jdbc/build.gradle b/modules/jdbc/build.gradle
index a9ab9761e4a..7641f596bd9 100644
--- a/modules/jdbc/build.gradle
+++ b/modules/jdbc/build.gradle
@@ -45,6 +45,7 @@ dependencies {
     integrationTestImplementation testFixtures(project(':ignite-runner'))
     integrationTestImplementation project(':ignite-transactions')
     integrationTestImplementation project(":ignite-runner")
+    integrationTestImplementation project(":ignite-client")
     integrationTestImplementation project(":ignite-api")
     integrationTestImplementation project(":ignite-system-view-api")
     integrationTestImplementation libs.awaitility
diff --git 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionFailoverTest.java
 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionFailoverTest.java
index c05492e8830..30243d5041b 100644
--- 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionFailoverTest.java
+++ 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionFailoverTest.java
@@ -27,9 +27,13 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.time.Duration;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
+import org.apache.ignite.internal.jdbc.JdbcConnection;
+import org.apache.ignite.internal.lang.RunnableX;
+import org.awaitility.Awaitility;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
@@ -50,7 +54,43 @@ public class ItJdbcConnectionFailoverTest extends 
ClusterPerTestIntegrationTest
      * <p>Test sequentially restarts each cluster node keeping CMG majority 
alive.
      */
     @Test
-    void testConnectionFailover() throws SQLException {
+    void testBasicQueryForwardedToAliveNode() throws Throwable {
+        int nodesCount = 3;
+        cluster.startAndInit(nodesCount, new int[]{0, 1, 2});
+
+        try (Connection connection = getConnection(nodesCount)) {
+            try (Statement statement = connection.createStatement()) {
+                Awaitility.await().until(() -> channelsCount(connection), 
is(nodesCount));
+
+                RunnableX query = () -> {
+                    for (int i = 0; i < 100; i++) {
+                        assertThat(statement.execute("SELECT " + i), is(true));
+                    }
+                };
+
+                query.run();
+
+                cluster.stopNode(0);
+
+                query.run();
+
+                cluster.startNode(0);
+                cluster.stopNode(1);
+                query.run();
+
+                cluster.startNode(1);
+                cluster.stopNode(2);
+                query.run();
+            }
+        }
+    }
+
+    /**
+     * Ensures that the partition aware query is forwarded to the alive node.
+     */
+    @Test
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-27180";)
+    void testPartitionAwareQueryForwardedToRandomNode() throws SQLException {
         int nodesCount = 3;
         cluster.startAndInit(nodesCount, new int[]{0, 1, 2});
 
@@ -59,6 +99,8 @@ public class ItJdbcConnectionFailoverTest extends 
ClusterPerTestIntegrationTest
                 statement.executeUpdate("CREATE ZONE zone1 (REPLICAS 3) 
STORAGE PROFILES ['default']");
                 statement.executeUpdate("CREATE TABLE t(id INT PRIMARY KEY, 
val INT) ZONE zone1");
 
+                Awaitility.await().until(() -> channelsCount(connection), 
is(nodesCount));
+
                 try (PreparedStatement preparedStatement = 
connection.prepareStatement("INSERT INTO t VALUES (?, ?)")) {
                     performUpdates(preparedStatement, 0, 100);
 
@@ -82,6 +124,50 @@ public class ItJdbcConnectionFailoverTest extends 
ClusterPerTestIntegrationTest
         }
     }
 
+    /**
+     * Ensures that the connection to a previously stopped node will be 
restored after the specified time interval.
+     *
+     * <p>Note: this test relies on the internal implementation to ensure that 
the
+     *          JDBC connection property is correctly applied to the 
underlying client.
+     */
+    @Test
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-27188";)
+    void testConnectionRestoredAfterBackgroundReconnectInterval() throws 
Exception {
+        int nodesCount = 3;
+        cluster.startAndInit(nodesCount, new int[]{2});
+        int backgroundReconnectInterval = 300;
+
+        try (Connection connection = getConnection(nodesCount, 
"backgroundReconnectIntervalMillis=" + backgroundReconnectInterval)) {
+            Awaitility.await().until(() -> channelsCount(connection), 
is(nodesCount));
+
+            cluster.stopNode(0);
+
+            assertThat(channelsCount(connection), is(nodesCount - 1));
+
+            cluster.startNode(0);
+
+            
Awaitility.await().atMost(Duration.ofMillis(backgroundReconnectInterval * 2))
+                    .until(() -> channelsCount(connection), is(nodesCount));
+        }
+
+        // No background reconnection is expected.
+        try (Connection connection = getConnection(nodesCount, 
"backgroundReconnectIntervalMillis=0")) {
+            Awaitility.await().until(() -> channelsCount(connection), 
is(nodesCount));
+
+            cluster.stopNode(0);
+
+            assertThat(channelsCount(connection), is(nodesCount - 1));
+
+            cluster.startNode(0);
+
+            // Ensure that no new connections occur during the specified 
background reconnect interval.
+            
Awaitility.await().during(Duration.ofMillis(backgroundReconnectInterval * 2))
+                    .until(() -> channelsCount(connection), is(nodesCount - 
1));
+
+            assertThat(channelsCount(connection), is(nodesCount - 1));
+        }
+    }
+
     /**
      * Checks transparent connection establishment after losing all 
connections.
      */
@@ -143,13 +229,19 @@ public class ItJdbcConnectionFailoverTest extends 
ClusterPerTestIntegrationTest
         }
     }
 
-    private static Connection getConnection(int nodesCount) throws 
SQLException {
+    private static Connection getConnection(int nodesCount, String ... params) 
throws SQLException {
         String addresses = IntStream.range(0, nodesCount)
                 .mapToObj(i -> "127.0.0.1:" + (BASE_CLIENT_PORT + i))
                 .collect(Collectors.joining(","));
 
+        return getConnection(addresses, params);
+    }
+
+    private static Connection getConnection(String addresses, String ... 
params) throws SQLException {
+        String args = String.join("&", params);
+
         //noinspection CallToDriverManagerGetConnection
-        return DriverManager.getConnection("jdbc:ignite:thin://" + addresses);
+        return DriverManager.getConnection("jdbc:ignite:thin://" + addresses + 
"?" + args);
     }
 
     private static void performUpdates(PreparedStatement preparedStatement, 
int start, int end) throws SQLException {
@@ -161,4 +253,10 @@ public class ItJdbcConnectionFailoverTest extends 
ClusterPerTestIntegrationTest
             assertThat(preparedStatement.executeUpdate(), is(1));
         }
     }
+
+    private static int channelsCount(Connection connection) throws 
SQLException {
+        JdbcConnection jdbcConnection = 
connection.unwrap(JdbcConnection.class);
+
+        return jdbcConnection.channelsCount();
+    }
 }
diff --git 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionSelfTest.java
 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionSelfTest.java
index 6dd2b956f25..8a733103768 100644
--- 
a/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionSelfTest.java
+++ 
b/modules/jdbc/src/integrationTest/java/org/apache/ignite/jdbc/ItJdbcConnectionSelfTest.java
@@ -27,6 +27,7 @@ import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
 import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
 import static java.sql.Statement.NO_GENERATED_KEYS;
 import static java.sql.Statement.RETURN_GENERATED_KEYS;
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.is;
@@ -53,6 +54,7 @@ import java.util.Properties;
 import java.util.ServiceLoader;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
+import org.apache.ignite.client.IgniteClientConfiguration;
 import org.apache.ignite.internal.jdbc.JdbcConnection;
 import org.apache.ignite.jdbc.util.JdbcTestUtils;
 import org.awaitility.Awaitility;
@@ -987,6 +989,14 @@ public class ItJdbcConnectionSelfTest extends 
AbstractJdbcSelfTest {
 
     @Test
     public void testChangePartitionAwarenessCacheSize() throws SQLException {
+        // Default value.
+        try (JdbcConnection conn = (JdbcConnection) 
DriverManager.getConnection(URL)) {
+            assertEquals(
+                    
IgniteClientConfiguration.DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE,
+                    conn.properties().getPartitionAwarenessMetadataCacheSize()
+            );
+        }
+
         String urlPrefix = URL + "?partitionAwarenessMetadataCacheSize";
 
         assertInvalid(urlPrefix + "=A",
@@ -1013,4 +1023,44 @@ public class ItJdbcConnectionSelfTest extends 
AbstractJdbcSelfTest {
 
         Awaitility.await().until(() -> ((JdbcConnection) 
conn).channelsCount(), is(initialNodes()));
     }
+
+    @Test
+    public void testChangeBackgroundReconnectInterval() throws SQLException {
+        String propertyName = "backgroundReconnectIntervalMillis";
+        String urlPrefix = URL + "?" + propertyName;
+
+        SqlThrowingFunction<String, Number> valueGetter = url -> {
+            try (JdbcConnection conn = (JdbcConnection) 
DriverManager.getConnection(url)) {
+                return conn.properties().getBackgroundReconnectInterval();
+            }
+        };
+
+        assertThat(valueGetter.apply(URL), 
is(IgniteClientConfiguration.DFLT_BACKGROUND_RECONNECT_INTERVAL));
+        assertThat(valueGetter.apply(urlPrefix + "=9223372036854775807"), 
is(Long.MAX_VALUE));
+        assertThat(valueGetter.apply(urlPrefix + "=0"), is(0L));
+
+        assertInvalid(urlPrefix + "=A",
+                format("Failed to parse int property [name={}, value=A]", 
propertyName));
+
+        assertInvalid(urlPrefix + "=-1",
+                format("Property cannot be lower than 0 [name={}, value=-1]", 
propertyName));
+
+        assertInvalid(urlPrefix + "=9223372036854775808",
+                format("Failed to parse int property [name={}, 
value=9223372036854775808]", propertyName));
+    }
+
+    /**
+     * Function that can throw an {@link SQLException}.
+     */
+    @FunctionalInterface
+    private interface SqlThrowingFunction<T, R> {
+        /**
+         * Applies the function to a given argument.
+         *
+         * @param t Argument.
+         * @return Application result.
+         * @throws SQLException If something goes wrong.
+         */
+        R apply(T t) throws SQLException;
+    }
 }
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
index 5ea1011f6f1..0e5da1f816d 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionProperties.java
@@ -98,6 +98,15 @@ public interface ConnectionProperties {
      */
     void setConnectionTimeout(Integer connTimeout) throws SQLException;
 
+    /**
+     * Gets the background reconnect interval, in milliseconds.
+     *
+     * <p>Value {@code 0} means that background reconnect is disabled.
+     *
+     * <p>Default is {@link 
org.apache.ignite.client.IgniteClientConfiguration#DFLT_BACKGROUND_RECONNECT_INTERVAL}.
+     */
+    long getBackgroundReconnectInterval();
+
     /**
      * SSL enabled.
      *
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
index 06cc457b306..ac0a31d69d3 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/ConnectionPropertiesImpl.java
@@ -70,6 +70,12 @@ public class ConnectionPropertiesImpl implements 
ConnectionProperties, Serializa
                     + " Zero means there is no limits.",
             0L, false, 0, Integer.MAX_VALUE);
 
+    /** JDBC background reconnect interval. */
+    private final LongProperty backgroundReconnectInterval = new 
LongProperty("backgroundReconnectIntervalMillis",
+            "Sets the background reconnect interval."
+                    + " Zero means that background reconnect is disabled.",
+            IgniteClientConfiguration.DFLT_BACKGROUND_RECONNECT_INTERVAL, 
false, 0, Long.MAX_VALUE);
+
     /** Path to the truststore. */
     private final StringProperty trustStorePath = new 
StringProperty("trustStorePath",
             "Path to trust store", null, null, false, null);
@@ -115,7 +121,8 @@ public class ConnectionPropertiesImpl implements 
ConnectionProperties, Serializa
     private final ConnectionProperty[] propsArray = {
             qryTimeout, connTimeout, trustStorePath, trustStorePassword,
             sslEnabled, ciphers, keyStorePath, keyStorePassword,
-            username, password, connectionTimeZone, 
partitionAwarenessMetadataCacheSize
+            username, password, connectionTimeZone, 
partitionAwarenessMetadataCacheSize,
+            backgroundReconnectInterval
     };
 
     /** {@inheritDoc} */
@@ -204,6 +211,12 @@ public class ConnectionPropertiesImpl implements 
ConnectionProperties, Serializa
         connTimeout.setValue(timeout);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public long getBackgroundReconnectInterval() {
+        return backgroundReconnectInterval.value();
+    }
+
     /** {@inheritDoc} */
     @Override
     public void setTrustStorePath(String trustStorePath) {
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index 60bff6ce232..9601848c080 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -47,7 +47,6 @@ import java.util.Properties;
 import java.util.concurrent.Executor;
 import java.util.concurrent.locks.ReentrantLock;
 import org.apache.ignite.client.IgniteClient;
-import org.apache.ignite.internal.client.TcpIgniteClient;
 import org.apache.ignite.internal.jdbc.proto.JdbcDatabaseMetadataHandler;
 import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
 import org.apache.ignite.internal.sql.SqlCommon;
@@ -837,7 +836,7 @@ public class JdbcConnection implements Connection {
 
     @TestOnly
     public int channelsCount() {
-        return ((TcpIgniteClient) igniteClient).channel().channels().size();
+        return igniteClient.connections().size();
     }
 
     private static void checkCursorOptions(
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/jdbc/IgniteJdbcDriver.java 
b/modules/jdbc/src/main/java/org/apache/ignite/jdbc/IgniteJdbcDriver.java
index 2a3ed145359..1277d981d50 100644
--- a/modules/jdbc/src/main/java/org/apache/ignite/jdbc/IgniteJdbcDriver.java
+++ b/modules/jdbc/src/main/java/org/apache/ignite/jdbc/IgniteJdbcDriver.java
@@ -36,7 +36,7 @@ import org.apache.ignite.client.BasicAuthenticator;
 import org.apache.ignite.client.IgniteClientAuthenticator;
 import org.apache.ignite.client.IgniteClientConfiguration;
 import org.apache.ignite.client.IgniteClientConnectionException;
-import org.apache.ignite.client.RetryLimitPolicy;
+import org.apache.ignite.client.RetryReadPolicy;
 import org.apache.ignite.client.SslConfiguration;
 import org.apache.ignite.internal.client.ChannelValidator;
 import org.apache.ignite.internal.client.HostAndPort;
@@ -115,6 +115,12 @@ import org.jetbrains.annotations.Nullable;
  *          <br>By default no any timeout.</td>
  *   </tr>
  *   <tr>
+ *      <td>backgroundReconnectIntervalMillis</td>
+ *      <td>Background reconnect interval, in milliseconds.
+ *          <br>The value {@code 0} can be used to disable background 
reconnection.
+ *          <br>The default value is {@code 30 000}.</td>
+ *   </tr>
+ *   <tr>
  *       <th colspan="2">Basic authentication</th>
  *   </tr>
  *   <tr>
@@ -303,11 +309,11 @@ public class IgniteJdbcDriver implements Driver {
                 null,
                 addresses,
                 networkTimeout,
-                
IgniteClientConfigurationImpl.DFLT_BACKGROUND_RECONNECT_INTERVAL,
+                connectionProperties.getBackgroundReconnectInterval(),
                 null,
                 IgniteClientConfigurationImpl.DFLT_HEARTBEAT_INTERVAL,
                 IgniteClientConfigurationImpl.DFLT_HEARTBEAT_TIMEOUT,
-                new RetryLimitPolicy(),
+                new RetryReadPolicy(),
                 null,
                 extractSslConfiguration(connectionProperties),
                 false,
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientChannelValidatorTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientChannelValidatorTest.java
index 53e3ed5bcf0..6d0ce4d5478 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientChannelValidatorTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientChannelValidatorTest.java
@@ -43,6 +43,7 @@ import org.apache.ignite.client.IgniteClient;
 import org.apache.ignite.client.IgniteClientConfiguration;
 import org.apache.ignite.client.IgniteClientConnectionException;
 import org.apache.ignite.client.RetryLimitPolicy;
+import org.apache.ignite.client.RetryReadPolicy;
 import org.apache.ignite.internal.client.ChannelValidator;
 import org.apache.ignite.internal.client.IgniteClientConfigurationImpl;
 import org.apache.ignite.internal.client.ProtocolContext;
@@ -207,7 +208,7 @@ public class ItThinClientChannelValidatorTest extends 
BaseIgniteAbstractTest {
                 null,
                 IgniteClientConfigurationImpl.DFLT_HEARTBEAT_INTERVAL,
                 IgniteClientConfigurationImpl.DFLT_HEARTBEAT_TIMEOUT,
-                new RetryLimitPolicy(),
+                new RetryReadPolicy(),
                 null,
                 null,
                 false,

Reply via email to