diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3298d7655..d7895ebe0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,30 @@
# Changelog
+## [2.35.1](https://github.com/googleapis/java-spanner-jdbc/compare/v2.35.0...v2.35.1) (2026-01-26)
+
+
+### Bug Fixes
+
+* Accept Arrays of Integer, Short, Byte for Array<Int64> ([#2365](https://github.com/googleapis/java-spanner-jdbc/issues/2365)) ([7429508](https://github.com/googleapis/java-spanner-jdbc/commit/7429508b82b26a55b6d7910416f3c72cfa63af2e))
+
+
+### Performance Improvements
+
+* Optimize JdbcDataSource#getConnection() ([#2371](https://github.com/googleapis/java-spanner-jdbc/issues/2371)) ([832064c](https://github.com/googleapis/java-spanner-jdbc/commit/832064ca2bca0d2d29e00c6b1417c7a4ce22f847))
+
+
+### Dependencies
+
+* Update dependency com.fasterxml.jackson.core:jackson-databind to v2.21.0 ([#2364](https://github.com/googleapis/java-spanner-jdbc/issues/2364)) ([a4b5c21](https://github.com/googleapis/java-spanner-jdbc/commit/a4b5c21a449fca70bab45eb756addcba57f0b080))
+* Update dependency org.springframework.boot:spring-boot to v4.0.2 ([#2369](https://github.com/googleapis/java-spanner-jdbc/issues/2369)) ([6c2b28e](https://github.com/googleapis/java-spanner-jdbc/commit/6c2b28e966dbb4c12a8c877b7eac2935146eaeb5))
+* Update dependency org.springframework.boot:spring-boot-starter-data-jdbc to v4.0.2 ([#2370](https://github.com/googleapis/java-spanner-jdbc/issues/2370)) ([61714fb](https://github.com/googleapis/java-spanner-jdbc/commit/61714fb5537d3676e7942738a86f08ec0b6e2707))
+* Update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.10 ([#2368](https://github.com/googleapis/java-spanner-jdbc/issues/2368)) ([db1755c](https://github.com/googleapis/java-spanner-jdbc/commit/db1755ca41b4c7fd0a232be94f66d5965677e46e))
+
+
+### Documentation
+
+* Update connection properties docs with DCP ([#2372](https://github.com/googleapis/java-spanner-jdbc/issues/2372)) ([094ab5a](https://github.com/googleapis/java-spanner-jdbc/commit/094ab5a62a723cd895a6ea54259a3bbef98d56fc))
+
## [2.35.0](https://github.com/googleapis/java-spanner-jdbc/compare/v2.34.1...v2.35.0) (2026-01-16)
diff --git a/README.md b/README.md
index c7e4a8b7b..5893ab94b 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ If you are using Maven, add this to your pom.xml file:
com.google.cloudgoogle-cloud-spanner-jdbc
- 2.35.0
+ 2.35.1
```
@@ -30,7 +30,7 @@ If you are using Gradle without BOM, add this to your dependencies
```Groovy
-implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.35.0'
+implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.35.1'
```
@@ -38,7 +38,7 @@ If you are using SBT, add this to your dependencies
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.35.0"
+libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.35.1"
```
diff --git a/documentation/connection_properties.md b/documentation/connection_properties.md
index 3e0f5efe3..99e3e1d16 100644
--- a/documentation/connection_properties.md
+++ b/documentation/connection_properties.md
@@ -22,6 +22,9 @@ The 'Context' value indicates whether the property can only be set when a connec
| credentialsprovider | The class name of the com.google.api.gax.core.CredentialsProvider implementation that should be used to obtain credentials for connections. | | | STARTUP |
| databaserole | Sets the database role to use for this connection. The default is privileges assigned to IAM role | | | STARTUP |
| databoostenabled | Enable data boost for all partitioned queries that are executed by this connection. This setting is only used for partitioned queries and is ignored by all other statements. | false | true, false | USER |
+| dcpinitialchannels | The initial number of channels in the dynamic channel pool. Only used when enableDynamicChannelPool is true. The default is SpannerOptions.DEFAULT_DYNAMIC_POOL_INITIAL_SIZE (4). | | | STARTUP |
+| dcpmaxchannels | The maximum number of channels in the dynamic channel pool. Only used when enableDynamicChannelPool is true. The default is SpannerOptions.DEFAULT_DYNAMIC_POOL_MAX_CHANNELS (10). | | | STARTUP |
+| dcpminchannels | The minimum number of channels in the dynamic channel pool. Only used when enableDynamicChannelPool is true. The default is SpannerOptions.DEFAULT_DYNAMIC_POOL_MIN_CHANNELS (2). | | | STARTUP |
| ddlintransactionmode | Determines how the connection should handle DDL statements in a read/write transaction. | ALLOW_IN_EMPTY_TRANSACTION | FAIL, ALLOW_IN_EMPTY_TRANSACTION, AUTO_COMMIT_TRANSACTION | USER |
| default_isolation_level | The transaction isolation level that is used by default for read/write transactions. The default is isolation_level_unspecified, which means that the connection will use the default isolation level of the database that it is connected to. | ISOLATION_LEVEL_UNSPECIFIED | ISOLATION_LEVEL_UNSPECIFIED, SERIALIZABLE, REPEATABLE_READ | USER |
| defaultsequencekind | The default sequence kind that should be used for the database. This property is only used when a DDL statement that requires a default sequence kind is executed on this connection. | | | USER |
@@ -30,6 +33,7 @@ The 'Context' value indicates whether the property can only be set when a connec
| directed_read | The directed read options to apply to read-only transactions. | | | USER |
| enableapitracing | Add OpenTelemetry traces for each individual RPC call. Enable this to get a detailed view of each RPC that is being executed by your application, or if you want to debug potential latency problems caused by RPCs that are being retried. | | true, false | STARTUP |
| enabledirectaccess | Configure the connection to try to connect to Spanner using DirectPath (true/false). The client will try to connect to Spanner using a direct Google network connection. DirectPath will work only if the client is trying to establish a connection from a Google Cloud VM. Otherwise it will automatically fallback to the standard network path. NOTE: The default for this property is currently false, but this could be changed in the future. | | true, false | STARTUP |
+| enabledynamicchannelpool | Enable dynamic channel pooling for automatic gRPC channel scaling. When enabled, the client will automatically scale the number of channels based on load. Setting numChannels will disable dynamic channel pooling even if this is set to true. The default is currently false (disabled), but this may change to true in a future version. Set this property explicitly to ensure consistent behavior. | | true, false | STARTUP |
| enableendtoendtracing | Enable end-to-end tracing (true/false) to generate traces for both the time that is spent in the client, as well as time that is spent in the Spanner server. Server side traces can only go to Google Cloud Trace, so to see end to end traces, the application should configure an exporter that exports the traces to Google Cloud Trace. | false | true, false | STARTUP |
| enableextendedtracing | Include the SQL string in the OpenTelemetry traces that are generated by this connection. The SQL string is added as the standard OpenTelemetry attribute 'db.statement'. | | true, false | STARTUP |
| encodedcredentials | Base64-encoded credentials to use for this connection. If neither this property or a credentials location are set, the connection will use the default Google Cloud credentials for the runtime environment. WARNING: Enabling this property without proper validation can expose the application to security risks. It is intended for use with credentials from a trusted source only, as it could otherwise allow end-users to supply arbitrary credentials. For more information, seehttps://cloud.google.com/docs/authentication/client-libraries#external-credentials | | | STARTUP |
diff --git a/pom.xml b/pom.xml
index d8ac3be2c..f299e9002 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0google-cloud-spanner-jdbc
- 2.35.0
+ 2.35.1jarGoogle Cloud Spanner JDBChttps://github.com/googleapis/java-spanner-jdbc
@@ -260,6 +260,7 @@
projects/gcloud-devel/instances/spanner-testing-east1
+ logging.properties
@@ -274,6 +275,7 @@
projects/gcloud-devel/instances/spanner-testing-east1
+ logging.properties24004
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index 78ae62ddd..865447824 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -29,7 +29,7 @@
com.google.cloudgoogle-cloud-spanner-jdbc
- 2.34.1
+ 2.35.0
diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml
index 7fb46c05d..4fe8c079e 100644
--- a/samples/quickperf/pom.xml
+++ b/samples/quickperf/pom.xml
@@ -27,7 +27,7 @@
com.google.cloudlibraries-bom
- 26.73.0
+ 26.74.0pomimport
@@ -67,7 +67,7 @@
com.fasterxml.jackson.corejackson-databind
- 2.20.1
+ 2.21.0
@@ -79,7 +79,7 @@
org.springframework.bootspring-boot
- 4.0.1
+ 4.0.2test
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index f190545bb..fc6ff5f0c 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -29,7 +29,7 @@
com.google.cloudgoogle-cloud-spanner-jdbc
- 2.35.0
+ 2.35.1
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index c52c6f644..666ba5664 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -26,7 +26,7 @@
com.google.cloudlibraries-bom
- 26.73.0
+ 26.74.0pomimport
diff --git a/samples/spring-data-jdbc/googlesql/pom.xml b/samples/spring-data-jdbc/googlesql/pom.xml
index d789c91a6..5029fe0e7 100644
--- a/samples/spring-data-jdbc/googlesql/pom.xml
+++ b/samples/spring-data-jdbc/googlesql/pom.xml
@@ -41,7 +41,7 @@
com.google.cloudlibraries-bom
- 26.73.0
+ 26.74.0importpom
@@ -59,7 +59,7 @@
org.springframework.bootspring-boot-starter-data-jdbc
- 4.0.1
+ 4.0.2
diff --git a/samples/spring-data-jdbc/postgresql/pom.xml b/samples/spring-data-jdbc/postgresql/pom.xml
index 984ecf62a..58978949e 100644
--- a/samples/spring-data-jdbc/postgresql/pom.xml
+++ b/samples/spring-data-jdbc/postgresql/pom.xml
@@ -41,7 +41,7 @@
com.google.cloudlibraries-bom
- 26.73.0
+ 26.74.0importpom
@@ -59,7 +59,7 @@
org.springframework.bootspring-boot-starter-data-jdbc
- 4.0.1
+ 4.0.2
diff --git a/samples/spring-data-mybatis/googlesql/pom.xml b/samples/spring-data-mybatis/googlesql/pom.xml
index 0917b8df9..3fa032f64 100644
--- a/samples/spring-data-mybatis/googlesql/pom.xml
+++ b/samples/spring-data-mybatis/googlesql/pom.xml
@@ -13,7 +13,7 @@
org.springframework.bootspring-boot-starter-parent
- 3.5.9
+ 3.5.10
@@ -46,7 +46,7 @@
com.google.cloudlibraries-bom
- 26.73.0
+ 26.74.0importpom
diff --git a/samples/spring-data-mybatis/postgresql/pom.xml b/samples/spring-data-mybatis/postgresql/pom.xml
index f5f1c33b0..eb262a031 100644
--- a/samples/spring-data-mybatis/postgresql/pom.xml
+++ b/samples/spring-data-mybatis/postgresql/pom.xml
@@ -13,7 +13,7 @@
org.springframework.bootspring-boot-starter-parent
- 3.5.9
+ 3.5.10
@@ -35,7 +35,7 @@
com.google.cloudlibraries-bom
- 26.73.0
+ 26.74.0importpom
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java
index 1dd83f817..3cb523186 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcArray.java
@@ -92,19 +92,41 @@ private JdbcArray(JdbcDataType type, Object[] elements) throws SQLException {
this.data =
java.lang.reflect.Array.newInstance(
elements.getClass().getComponentType(), elements.length);
+ System.arraycopy(elements, 0, this.data, 0, elements.length);
+ } else if (type == JdbcDataType.INT64 && requiresWideningToLong(elements)) {
+ // Convert Byte[], Short[], and Integer[] to Long[] for INT64 type
+ // since Spanner only supports ARRAY
+ this.data = convertToLongArray(elements);
} else {
this.data = java.lang.reflect.Array.newInstance(type.getJavaClass(), elements.length);
+ try {
+ System.arraycopy(elements, 0, this.data, 0, elements.length);
+ } catch (Exception e) {
+ throw JdbcSqlExceptionFactory.of(
+ "Could not copy array elements. Make sure the supplied array only contains elements of class "
+ + type.getJavaClass().getName(),
+ Code.UNKNOWN,
+ e);
+ }
}
- try {
- System.arraycopy(elements, 0, this.data, 0, elements.length);
- } catch (Exception e) {
- throw JdbcSqlExceptionFactory.of(
- "Could not copy array elements. Make sure the supplied array only contains elements of class "
- + type.getJavaClass().getName(),
- Code.UNKNOWN,
- e);
+ }
+ }
+
+ private static boolean requiresWideningToLong(Object[] elements) {
+ Class> componentType = elements.getClass().getComponentType();
+ return componentType == Byte.class
+ || componentType == Short.class
+ || componentType == Integer.class;
+ }
+
+ private static Long[] convertToLongArray(Object[] elements) {
+ Long[] longElements = new Long[elements.length];
+ for (int i = 0; i < elements.length; i++) {
+ if (elements[i] != null) {
+ longElements[i] = ((Number) elements[i]).longValue();
}
}
+ return longElements;
}
private JdbcArray(JdbcDataType type, List> elements) {
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java
index d4f415d27..f0f073aad 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java
@@ -16,11 +16,14 @@
package com.google.cloud.spanner.jdbc;
+import static com.google.cloud.spanner.jdbc.JdbcDriver.appendPropertiesToUrl;
+import static com.google.cloud.spanner.jdbc.JdbcDriver.buildConnectionOptions;
+import static com.google.cloud.spanner.jdbc.JdbcDriver.maybeAddUserAgent;
+
import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.rpc.Code;
import java.io.PrintWriter;
import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
@@ -35,6 +38,8 @@ public class JdbcDataSource extends AbstractJdbcWrapper implements DataSource {
private Boolean readonly;
private Boolean retryAbortsInternally;
+ private volatile ConnectionOptions cachedConnectionOptions;
+
// Make sure the JDBC driver class is loaded.
static {
try {
@@ -76,12 +81,22 @@ public Connection getConnection() throws SQLException {
throw JdbcSqlExceptionFactory.of(
"There is no URL specified for this data source", Code.FAILED_PRECONDITION);
}
- if (!JdbcDriver.getRegisteredDriver().acceptsURL(getUrl())) {
- throw JdbcSqlExceptionFactory.of(
- "The URL " + getUrl() + " is not valid for the data source " + getClass().getName(),
- Code.FAILED_PRECONDITION);
+ if (cachedConnectionOptions == null) {
+ synchronized (this) {
+ if (cachedConnectionOptions == null) {
+ if (!JdbcDriver.getRegisteredDriver().acceptsURL(getUrl())) {
+ throw JdbcSqlExceptionFactory.of(
+ "The URL " + getUrl() + " is not valid for the data source " + getClass().getName(),
+ Code.FAILED_PRECONDITION);
+ }
+ Properties properties = createProperties();
+ maybeAddUserAgent(properties);
+ String connectionUri = appendPropertiesToUrl(url.substring(5), properties);
+ cachedConnectionOptions = buildConnectionOptions(connectionUri, properties);
+ }
+ }
}
- return DriverManager.getConnection(getUrl(), createProperties());
+ return new JdbcConnection(getUrl(), cachedConnectionOptions);
}
@Override
@@ -114,6 +129,12 @@ public boolean isClosed() {
return false;
}
+ private void clearCachedConnectionOptions() {
+ synchronized (this) {
+ cachedConnectionOptions = null;
+ }
+ }
+
/**
* @return the JDBC URL to use for this {@link DataSource}.
*/
@@ -125,6 +146,7 @@ public String getUrl() {
* @param url The JDBC URL to use for this {@link DataSource}.
*/
public void setUrl(String url) {
+ clearCachedConnectionOptions();
this.url = url;
}
@@ -143,6 +165,7 @@ public String getCredentials() {
* connection URL will be used.
*/
public void setCredentials(String credentials) {
+ clearCachedConnectionOptions();
this.credentials = credentials;
}
@@ -161,6 +184,7 @@ public Boolean getAutocommit() {
* the connection URL will be used.
*/
public void setAutocommit(Boolean autocommit) {
+ clearCachedConnectionOptions();
this.autocommit = autocommit;
}
@@ -179,6 +203,7 @@ public Boolean getReadonly() {
* URL will be used.
*/
public void setReadonly(Boolean readonly) {
+ clearCachedConnectionOptions();
this.readonly = readonly;
}
@@ -197,6 +222,7 @@ public Boolean getRetryAbortsInternally() {
* this property, the value in the connection URL will be used.
*/
public void setRetryAbortsInternally(Boolean retryAbortsInternally) {
+ clearCachedConnectionOptions();
this.retryAbortsInternally = retryAbortsInternally;
}
}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
index bb2f7c007..892c0057c 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
@@ -201,7 +201,7 @@ public Set getPostgreSQLAliases() {
},
INT64 {
private final Set> classes =
- new HashSet<>(Arrays.asList(Byte.class, Integer.class, Long.class));
+ new HashSet<>(Arrays.asList(Byte.class, Short.class, Integer.class, Long.class));
@Override
public int getSqlType() {
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
index 120880fee..8e8bd0726 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
@@ -37,6 +37,7 @@
import java.sql.SQLWarning;
import java.util.Map.Entry;
import java.util.Properties;
+import java.util.function.Supplier;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -247,7 +248,7 @@ public Connection connect(String url, Properties info) throws SQLException {
return null;
}
- private ConnectionOptions buildConnectionOptions(String connectionUrl, Properties info) {
+ static ConnectionOptions buildConnectionOptions(String connectionUrl, Properties info) {
ConnectionOptions.Builder builder =
ConnectionOptions.newBuilder().setTracingPrefix("JDBC").setUri(connectionUrl);
if (info.containsKey(OPEN_TELEMETRY_PROPERTY_KEY)
@@ -272,40 +273,42 @@ static void maybeAddUserAgent(Properties properties) {
}
}
- static boolean isHibernate() {
- // Cache the result as the check is relatively expensive, and we also don't want to create
- // multiple different Spanner instances just to get the correct user-agent in every case.
- return Suppliers.memoize(
- () -> {
- try {
- // First check if the Spanner Hibernate dialect is on the classpath. If it is, then
- // we assume that Hibernate will (eventually) be used.
- Class.forName(
- "com.google.cloud.spanner.hibernate.SpannerDialect",
- /* initialize= */ false,
- JdbcDriver.class.getClassLoader());
- return true;
- } catch (Throwable ignore) {
- }
+ private static final Supplier isHibernate =
+ Suppliers.memoize(
+ () -> {
+ try {
+ // First check if the Spanner Hibernate dialect is on the classpath. If it is, then
+ // we assume that Hibernate will (eventually) be used.
+ Class.forName(
+ "com.google.cloud.spanner.hibernate.SpannerDialect",
+ /* initialize= */ false,
+ JdbcDriver.class.getClassLoader());
+ return true;
+ } catch (Throwable ignore) {
+ }
- // If we did not find the Spanner Hibernate dialect on the classpath, then do a
- // check if the connection is still being created by Hibernate using the built-in
- // Spanner dialect in Hibernate.
- try {
- StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
- for (StackTraceElement element : callStack) {
- if (element.getClassName().contains(".hibernate.")) {
- return true;
- }
+ // If we did not find the Spanner Hibernate dialect on the classpath, then do a
+ // check if the connection is still being created by Hibernate using the built-in
+ // Spanner dialect in Hibernate.
+ try {
+ StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ for (StackTraceElement element : callStack) {
+ if (element.getClassName().contains(".hibernate.")) {
+ return true;
}
- } catch (Throwable ignore) {
}
- return false;
- })
- .get();
+ } catch (Throwable ignore) {
+ }
+ return false;
+ });
+
+ static boolean isHibernate() {
+ // Cache the result as the check is relatively expensive, and we also don't want to create
+ // multiple different Spanner instances just to get the correct user-agent in every case.
+ return isHibernate.get();
}
- private String appendPropertiesToUrl(String url, Properties info) {
+ static String appendPropertiesToUrl(String url, Properties info) {
StringBuilder res = new StringBuilder(url);
for (Entry