diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4dd9974f2..b9b8be0c3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - java: [11, 17, 21] + java: [11, 17, 21, 25] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml index 7394b52af..2cf303dc3 100644 --- a/.github/workflows/unmanaged_dependency_check.yaml +++ b/.github/workflows/unmanaged_dependency_check.yaml @@ -14,6 +14,6 @@ jobs: shell: bash run: .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.52.2 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.52.3 with: bom-path: pom.xml diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg index 631631deb..85695810d 100644 --- a/.kokoro/presubmit/graalvm-native-a.cfg +++ b/.kokoro/presubmit/graalvm-native-a.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.52.2" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_a:3.52.3" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg index 16cf471b0..c2efdcebf 100644 --- a/.kokoro/presubmit/graalvm-native-b.cfg +++ b/.kokoro/presubmit/graalvm-native-b.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.52.2" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_b:3.52.3" } env_vars: { diff --git a/.kokoro/presubmit/graalvm-native-c.cfg b/.kokoro/presubmit/graalvm-native-c.cfg index 5d2697122..20946e7b0 100644 --- a/.kokoro/presubmit/graalvm-native-c.cfg +++ b/.kokoro/presubmit/graalvm-native-c.cfg @@ -3,7 +3,7 @@ # Configure the docker image for kokoro-trampoline. env_vars: { key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.52.2" + value: "gcr.io/cloud-devrel-public-resources/graalvm_sdk_platform_c:3.52.3" } env_vars: { diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f6d8d3f..d5fe61cbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [2.33.1](https://github.com/googleapis/java-spanner-jdbc/compare/v2.33.0...v2.33.1) (2025-10-09) + + +### Dependencies + +* Update dependency com.google.api.grpc:proto-google-cloud-trace-v1 to v2.76.0 ([#2240](https://github.com/googleapis/java-spanner-jdbc/issues/2240)) ([442565e](https://github.com/googleapis/java-spanner-jdbc/commit/442565ea8bf22ee926126fbd11a68a869b0772c7)) +* Update dependency com.google.cloud:google-cloud-spanner to v6.101.1 ([#2227](https://github.com/googleapis/java-spanner-jdbc/issues/2227)) ([275c6bd](https://github.com/googleapis/java-spanner-jdbc/commit/275c6bd7f3f58b3f980e9ffaa576d1f910ecf08b)) +* Update dependency com.google.cloud:google-cloud-spanner to v6.102.0 ([#2244](https://github.com/googleapis/java-spanner-jdbc/issues/2244)) ([383a392](https://github.com/googleapis/java-spanner-jdbc/commit/383a39222c7526163323575734c8473a34d4098d)) +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.101.1 ([#2228](https://github.com/googleapis/java-spanner-jdbc/issues/2228)) ([ded7944](https://github.com/googleapis/java-spanner-jdbc/commit/ded79447b8ecd294e56560b43cb02e45464706f1)) +* Update dependency com.google.cloud:google-cloud-spanner-bom to v6.102.0 ([#2246](https://github.com/googleapis/java-spanner-jdbc/issues/2246)) ([7162ac2](https://github.com/googleapis/java-spanner-jdbc/commit/7162ac236a77c24b4f0faccefa2cf3bdee6b96ab)) +* Update dependency com.google.cloud:google-cloud-trace to v2.76.0 ([#2241](https://github.com/googleapis/java-spanner-jdbc/issues/2241)) ([ff2432d](https://github.com/googleapis/java-spanner-jdbc/commit/ff2432d46cd23df2230671297f06c8e6fca8f1d7)) +* Update dependency com.google.cloud:sdk-platform-java-config to v3.52.3 ([#2236](https://github.com/googleapis/java-spanner-jdbc/issues/2236)) ([9a44975](https://github.com/googleapis/java-spanner-jdbc/commit/9a44975e0ab7ca49514be1d9ea03e8e760a0f3bc)) +* Update dependency net.bytebuddy:byte-buddy to v1.17.8 ([#2245](https://github.com/googleapis/java-spanner-jdbc/issues/2245)) ([60a3a8f](https://github.com/googleapis/java-spanner-jdbc/commit/60a3a8f0fcf86742128f8803ee3297670b713923)) +* Update dependency net.bytebuddy:byte-buddy-agent to v1.17.8 ([#2243](https://github.com/googleapis/java-spanner-jdbc/issues/2243)) ([952c08a](https://github.com/googleapis/java-spanner-jdbc/commit/952c08afbf2f7838ad864b8bb58423e8b566d325)) + + +### Documentation + +* Add samples for transaction isolation level ([#2030](https://github.com/googleapis/java-spanner-jdbc/issues/2030)) ([ca243d1](https://github.com/googleapis/java-spanner-jdbc/commit/ca243d194ef392d44cea561a148c0376b9af630d)) + ## [2.33.0](https://github.com/googleapis/java-spanner-jdbc/compare/v2.32.3...v2.33.0) (2025-09-27) diff --git a/README.md b/README.md index 61b3d5cb2..8fb6a054d 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.cloud google-cloud-spanner-jdbc - 2.33.0 + 2.33.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.33.0' +implementation 'com.google.cloud:google-cloud-spanner-jdbc:2.33.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.33.0" +libraryDependencies += "com.google.cloud" % "google-cloud-spanner-jdbc" % "2.33.1" ``` @@ -109,6 +109,7 @@ See [Supported Connection Properties](documentation/connection_properties.md) fo supported connection properties. #### Commonly Used Properties +- default_isolation_level (String): Spanner supports isolation levels REPEATABLE_READ or SERIALIZABLE. SERIALIZABLE is the default. Using isolation level REPEATABLE_READ improves performance by reducing the amount of locks that are taken by transactions that execute a large number of queries in read/write transactions. See https://cloud.google.com/spanner/docs/isolation-levels for more information on the supported isolation levels in Spanner. - credentials (String): URL for the credentials file to use for the connection. If you do not specify any credentials at all, the default credentials of the environment as returned by `GoogleCredentials#getApplicationDefault()` is used. Example: `jdbc:cloudspanner:/projects/my-project/instances/my-instance/databases/my-db;credentials=/path/to/credentials.json` - autocommit (boolean): Sets the initial autocommit mode for the connection. Default is true. - readonly (boolean): Sets the initial readonly mode for the connection. Default is false. diff --git a/documentation/latency-debugging-guide.md b/documentation/latency-debugging-guide.md index cdcfec826..0d2084c3f 100644 --- a/documentation/latency-debugging-guide.md +++ b/documentation/latency-debugging-guide.md @@ -5,6 +5,43 @@ queries and transactions and to determine whether transactions or requests are b addition, all metrics described in [Latency points in a Spanner request](https://cloud.google.com/spanner/docs/latency-points) are also collected by the JDBC driver and can be used for debugging. +## Isolation Level + +A common reason for high latency in read/write transactions is lock contention. Spanner by default +uses isolation level `SERIALIZABLE`. This causes all queries in read/write transactions to take +locks for all rows that are scanned by a query. Using isolation level `REPEATABLE_READ` reduces the +number of locks that are taken during a read/write transaction, and can significantly improve +performance for applications that execute many and/or large queries in read/write transactions. + +Enable isolation level `REPEATABLE_READ` by default for all transactions that are executed by the +JDBC driver by setting the `default_isolation_level` connection property like this in the connection +URL: + +```java +String projectId = "my-project"; +String instanceId = "my-instance"; +String databaseId = "my-database"; +String isolationLevel = "REPEATABLE_READ"; + +try (Connection connection = + DriverManager.getConnection( + String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?default_isolation_level=%s", + projectId, instanceId, databaseId, isolationLevel))) { + try (Statement statement = connection.createStatement()) { + try (ResultSet rs = statement.executeQuery("SELECT CURRENT_TIMESTAMP()")) { + while (rs.next()) { + System.out.printf( + "Connected to Cloud Spanner at [%s]%n", rs.getTimestamp(1).toString()); + } + } + } +} +``` + +See https://cloud.google.com/spanner/docs/isolation-levels for more information on the supported +isolation levels in Spanner. + ## Configuration You can configure the OpenTelemetry instance that should be used in two ways: diff --git a/pom.xml b/pom.xml index b457a2356..fcd478ebb 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.0 google-cloud-spanner-jdbc - 2.33.0 + 2.33.1 jar Google Cloud Spanner JDBC https://github.com/googleapis/java-spanner-jdbc @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.52.2 + 3.52.3 @@ -61,7 +61,7 @@ com.google.cloud google-cloud-spanner-bom - 6.101.1 + 6.102.0 pom import @@ -216,13 +216,13 @@ com.google.cloud google-cloud-trace - 2.75.0 + 2.76.0 test com.google.api.grpc proto-google-cloud-trace-v1 - 2.75.0 + 2.76.0 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 06eab9662..041fab8ac 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.32.3 + 2.33.0 diff --git a/samples/quickperf/pom.xml b/samples/quickperf/pom.xml index 1a79d8825..ca7335c4f 100644 --- a/samples/quickperf/pom.xml +++ b/samples/quickperf/pom.xml @@ -12,7 +12,7 @@ com.google.cloud sdk-platform-java-config - 3.52.2 + 3.52.3 @@ -27,7 +27,7 @@ com.google.cloud libraries-bom - 26.68.0 + 26.69.0 pom import @@ -95,7 +95,7 @@ org.codehaus.mojo exec-maven-plugin - 3.5.1 + 3.6.1 com.google.cloud.jdbc.quickperf.QuickPerf diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 59afa6f0d..46627619c 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-spanner-jdbc - 2.33.0 + 2.33.1 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index e82086db7..07c7ab457 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -9,7 +9,7 @@ com.google.cloud sdk-platform-java-config - 3.52.2 + 3.52.3 @@ -26,7 +26,7 @@ com.google.cloud libraries-bom - 26.68.0 + 26.69.0 pom import diff --git a/samples/snippets/src/main/java/com/example/spanner/jdbc/IsolationLevel.java b/samples/snippets/src/main/java/com/example/spanner/jdbc/IsolationLevel.java new file mode 100644 index 000000000..7153b7c7a --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/jdbc/IsolationLevel.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed 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 com.example.spanner.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Properties; + +final class IsolationLevel { + + static void isolationLevel( + final String project, + final String instance, + final String database, + final Properties properties) + throws SQLException { + String url = String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s", + project, instance, database); + try (Connection connection = DriverManager.getConnection(url, properties)) { + connection.setAutoCommit(false); + + // Spanner supports setting the isolation level to: + // 1. TRANSACTION_SERIALIZABLE (this is the default) + // 2. TRANSACTION_REPEATABLE_READ + + // The following line sets the default isolation level that will be used + // for all read/write transactions on this connection. + connection.setTransactionIsolation( + Connection.TRANSACTION_REPEATABLE_READ); + + // This query will not take any locks when using + // isolation level repeatable read. + try (ResultSet resultSet = connection + .createStatement() + .executeQuery("SELECT SingerId, Active " + + "FROM Singers " + + "ORDER BY LastName")) { + while (resultSet.next()) { + try (PreparedStatement statement = connection.prepareStatement( + "INSERT OR UPDATE INTO SingerHistory " + + "(SingerId, Active, CreatedAt) " + + "VALUES (?, ?, CURRENT_TIMESTAMP)")) { + statement.setLong(1, resultSet.getLong(1)); + statement.setBoolean(2, resultSet.getBoolean(2)); + statement.executeUpdate(); + } + } + } + connection.commit(); + } + } + + private IsolationLevel() { + } +} diff --git a/samples/snippets/src/test/java/com/example/spanner/jdbc/IsolationLevelTest.java b/samples/snippets/src/test/java/com/example/spanner/jdbc/IsolationLevelTest.java new file mode 100644 index 000000000..e1c7bfbe0 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/jdbc/IsolationLevelTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed 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 com.example.spanner.jdbc; + +import static com.example.spanner.jdbc.IsolationLevel.isolationLevel; +import static org.junit.Assume.assumeTrue; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.PullPolicy; +import org.testcontainers.utility.DockerImageName; + +@RunWith(JUnit4.class) +public class IsolationLevelTest { + + private static GenericContainer emulator; + + private static final String PROJECT = "emulator-project"; + private static final String INSTANCE = "my-instance"; + private static final String DATABASE = "my-database"; + private static final Properties PROPERTIES = new Properties(); + + @BeforeClass + public static void setupEmulator() throws Exception { + assumeTrue("This test requires Docker", DockerClientFactory.instance().isDockerAvailable()); + + emulator = + new GenericContainer<>(DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator")); + emulator.withImagePullPolicy(PullPolicy.alwaysPull()); + emulator.addExposedPort(9010); + emulator.setWaitStrategy(Wait.forListeningPorts(9010)); + emulator.start(); + + PROPERTIES.setProperty("endpoint", + String.format("localhost:%d", emulator.getMappedPort(9010))); + PROPERTIES.setProperty("autoConfigEmulator", "true"); + + String url = String.format( + "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s", + PROJECT, INSTANCE, DATABASE); + try (Connection connection = DriverManager.getConnection(url, PROPERTIES)) { + try (Statement statement = connection.createStatement()) { + statement.addBatch( + "CREATE TABLE Singers (" + + "SingerId INT64 PRIMARY KEY, " + + "FirstName STRING(MAX), " + + "LastName STRING(MAX), " + + "Active BOOL)"); + statement.addBatch( + "CREATE TABLE SingerHistory (" + + "SingerId INT64, " + + "Active BOOL, " + + "CreatedAt TIMESTAMP) " + + "PRIMARY KEY (SingerId, CreatedAt)"); + statement.executeBatch(); + } + } + } + + @AfterClass + public static void stopEmulator() { + if (emulator != null) { + emulator.stop(); + } + } + + @Test + public void testIsolationLevel() throws SQLException { + isolationLevel("emulator-project", "my-instance", "my-database", PROPERTIES); + } + +} diff --git a/samples/spring-data-jdbc/googlesql/pom.xml b/samples/spring-data-jdbc/googlesql/pom.xml index 31db916ba..be86aa508 100644 --- a/samples/spring-data-jdbc/googlesql/pom.xml +++ b/samples/spring-data-jdbc/googlesql/pom.xml @@ -30,14 +30,14 @@ com.google.cloud google-cloud-spanner-bom - 6.100.0 + 6.102.0 import pom com.google.cloud libraries-bom - 26.68.0 + 26.69.0 import pom diff --git a/samples/spring-data-jdbc/postgresql/pom.xml b/samples/spring-data-jdbc/postgresql/pom.xml index aa78ad2eb..b7305065b 100644 --- a/samples/spring-data-jdbc/postgresql/pom.xml +++ b/samples/spring-data-jdbc/postgresql/pom.xml @@ -30,14 +30,14 @@ com.google.cloud google-cloud-spanner-bom - 6.100.0 + 6.102.0 import pom com.google.cloud libraries-bom - 26.68.0 + 26.69.0 import pom @@ -110,6 +110,19 @@ testlib test + + + net.bytebuddy + byte-buddy + 1.17.8 + test + + + net.bytebuddy + byte-buddy-agent + 1.17.8 + test + junit junit diff --git a/samples/spring-data-mybatis/googlesql/pom.xml b/samples/spring-data-mybatis/googlesql/pom.xml index b4aa07c1d..0734042ae 100644 --- a/samples/spring-data-mybatis/googlesql/pom.xml +++ b/samples/spring-data-mybatis/googlesql/pom.xml @@ -35,14 +35,14 @@ com.google.cloud google-cloud-spanner-bom - 6.100.0 + 6.102.0 import pom com.google.cloud libraries-bom - 26.68.0 + 26.69.0 import pom @@ -94,7 +94,7 @@ com.google.cloud google-cloud-spanner - 6.100.0 + 6.102.0 test-jar test @@ -104,6 +104,19 @@ testlib test + + + net.bytebuddy + byte-buddy + 1.17.8 + test + + + net.bytebuddy + byte-buddy-agent + 1.17.8 + test + junit junit diff --git a/samples/spring-data-mybatis/postgresql/pom.xml b/samples/spring-data-mybatis/postgresql/pom.xml index 5f2b06f55..449634a8a 100644 --- a/samples/spring-data-mybatis/postgresql/pom.xml +++ b/samples/spring-data-mybatis/postgresql/pom.xml @@ -35,7 +35,7 @@ com.google.cloud libraries-bom - 26.68.0 + 26.69.0 import pom @@ -101,6 +101,19 @@ testlib test + + + net.bytebuddy + byte-buddy + 1.17.8 + test + + + net.bytebuddy + byte-buddy-agent + 1.17.8 + test + junit junit diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java index 7dc4667e4..d5a429078 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.connection.ConnectionOptions; import com.google.cloud.spanner.connection.ConnectionOptionsTest; @@ -555,8 +555,8 @@ public void testGetVersionColumns() throws SQLException { @Test public void testGetUserName() throws SQLException, IOException { - GoogleCredentials credentials = - GoogleCredentials.fromStream( + ServiceAccountCredentials credentials = + ServiceAccountCredentials.fromStream( Objects.requireNonNull(ConnectionOptionsTest.class.getResource("test-key.json")) .openStream()); JdbcConnection connection = mock(JdbcConnection.class); diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTransactionOptionsTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTransactionOptionsTest.java index a34e449e7..8a5af27c0 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTransactionOptionsTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTransactionOptionsTest.java @@ -27,10 +27,15 @@ import com.google.cloud.spanner.connection.AbstractMockServerTest; import com.google.cloud.spanner.connection.SpannerPool; import com.google.spanner.v1.CommitRequest; +import com.google.spanner.v1.ExecuteSqlRequest; +import com.google.spanner.v1.TransactionOptions.IsolationLevel; +import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.time.Duration; +import java.util.Arrays; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -138,4 +143,63 @@ public void testMaxCommitDelay() throws SQLException { assertEquals(Duration.ofMillis(50).toNanos(), request.getMaxCommitDelay().getNanos()); } } + + @Test + public void testDefaultIsolationLevel() throws SQLException { + for (IsolationLevel isolationLevel : + Arrays.stream(IsolationLevel.values()) + .filter(level -> !level.equals(IsolationLevel.UNRECOGNIZED)) + .collect(Collectors.toList())) { + try (java.sql.Connection connection = + DriverManager.getConnection( + "jdbc:" + getBaseUrl() + ";default_isolation_level=" + isolationLevel.name())) { + connection.setAutoCommit(false); + try (ResultSet resultSet = + connection.createStatement().executeQuery(SELECT1_STATEMENT.getSql())) { + while (resultSet.next()) { + // ignore + } + } + connection.commit(); + assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)); + ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0); + assertTrue(request.hasTransaction()); + assertTrue(request.getTransaction().hasBegin()); + assertTrue(request.getTransaction().getBegin().hasReadWrite()); + assertEquals(isolationLevel, request.getTransaction().getBegin().getIsolationLevel()); + assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class)); + + mockSpanner.clearRequests(); + } + } + } + + @Test + public void testSetIsolationLevel() throws SQLException { + try (java.sql.Connection connection = createJdbcConnection()) { + connection.setAutoCommit(false); + for (int isolationLevel : + new int[] {Connection.TRANSACTION_REPEATABLE_READ, Connection.TRANSACTION_SERIALIZABLE}) { + connection.setTransactionIsolation(isolationLevel); + try (ResultSet resultSet = + connection.createStatement().executeQuery(SELECT1_STATEMENT.getSql())) { + while (resultSet.next()) { + // ignore + } + } + connection.commit(); + assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class)); + ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0); + assertTrue(request.hasTransaction()); + assertTrue(request.getTransaction().hasBegin()); + assertTrue(request.getTransaction().getBegin().hasReadWrite()); + assertEquals( + IsolationLevelConverter.convertToSpanner(isolationLevel), + request.getTransaction().getBegin().getIsolationLevel()); + assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class)); + + mockSpanner.clearRequests(); + } + } + } } diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcConnectTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcConnectTest.java index b42e81e5e..92e590e16 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcConnectTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcConnectTest.java @@ -20,6 +20,7 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ParallelIntegrationTest; @@ -230,7 +231,8 @@ public void testConnectWithDataSourceWithConflictingValues() throws SQLException public void testConnectWithOAuthToken() throws Exception { GoogleCredentials credentials; if (hasValidKeyFile()) { - credentials = GoogleCredentials.fromStream(Files.newInputStream(Paths.get(getKeyFile()))); + credentials = + ServiceAccountCredentials.fromStream(Files.newInputStream(Paths.get(getKeyFile()))); } else { try { credentials = GoogleCredentials.getApplicationDefault(); diff --git a/versions.txt b/versions.txt index 4edb3f7a6..515f0d555 100644 --- a/versions.txt +++ b/versions.txt @@ -1,4 +1,4 @@ # Format: # module:released-version:current-version -google-cloud-spanner-jdbc:2.33.0:2.33.0 +google-cloud-spanner-jdbc:2.33.1:2.33.1