Skip to content

Commit 0234fb3

Browse files
authored
feat: concurrent transactions on emulator (#1578)
The JDBC driver now supports running multiple concurrent transactions on the emulator using a single thread. The emulator still only supports one read/write transaction per database at any time, but the JDBC driver works around this by manually setting a Savepoint after each statement, and using that Savepoint to automatically resume the transaction when it has been aborted by the emulator.
1 parent 5a017c5 commit 0234fb3

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@
171171
<type>test-jar</type>
172172
<scope>test</scope>
173173
</dependency>
174+
<dependency>
175+
<groupId>org.testcontainers</groupId>
176+
<artifactId>testcontainers</artifactId>
177+
<version>1.19.7</version>
178+
<scope>test</scope>
179+
</dependency>
174180
<dependency>
175181
<groupId>com.google.truth</groupId>
176182
<artifactId>truth</artifactId>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner.jdbc;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
22+
import static org.junit.Assume.assumeTrue;
23+
24+
import com.google.cloud.spanner.connection.SpannerPool;
25+
import java.sql.Connection;
26+
import java.sql.DriverManager;
27+
import java.sql.ResultSet;
28+
import java.sql.SQLException;
29+
import java.util.Properties;
30+
import org.junit.AfterClass;
31+
import org.junit.BeforeClass;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
import org.junit.runners.JUnit4;
35+
import org.testcontainers.DockerClientFactory;
36+
import org.testcontainers.containers.GenericContainer;
37+
import org.testcontainers.containers.wait.strategy.Wait;
38+
import org.testcontainers.utility.DockerImageName;
39+
40+
@RunWith(JUnit4.class)
41+
public class ConcurrentTransactionOnEmulatorTest {
42+
private static GenericContainer<?> emulator;
43+
44+
private static Properties properties;
45+
46+
@BeforeClass
47+
public static void startEmulator() {
48+
assumeTrue(DockerClientFactory.instance().isDockerAvailable());
49+
50+
emulator =
51+
new GenericContainer<>(
52+
DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator:latest"))
53+
.withExposedPorts(9010)
54+
.waitingFor(Wait.forListeningPort());
55+
emulator.start();
56+
properties = new Properties();
57+
properties.setProperty("autoConfigEmulator", "true");
58+
properties.setProperty(
59+
"endpoint", String.format("%s:%d", emulator.getHost(), emulator.getMappedPort(9010)));
60+
}
61+
62+
@AfterClass
63+
public static void cleanup() {
64+
SpannerPool.closeSpannerPool();
65+
if (emulator != null) {
66+
emulator.stop();
67+
}
68+
}
69+
70+
@Test
71+
public void testRunConcurrentTransactions() throws SQLException {
72+
String connectionUrl =
73+
String.format(
74+
"jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s",
75+
"emulator-project", "test-instance", "test-database");
76+
try (Connection connection1 = DriverManager.getConnection(connectionUrl, properties);
77+
Connection connection2 = DriverManager.getConnection(connectionUrl, properties)) {
78+
// Create a test table.
79+
connection1
80+
.createStatement()
81+
.execute("create table test (id int64, value string(max)) primary key (id)");
82+
83+
// Put both connections into autoCommit=false mode and verify that both connections can run
84+
// a transaction using a single thread.
85+
connection1.setAutoCommit(false);
86+
connection2.setAutoCommit(false);
87+
88+
connection1.createStatement().executeUpdate("insert into test (id, value) values (1, 'One')");
89+
connection2.createStatement().executeUpdate("insert into test (id, value) values (2, 'Two')");
90+
connection1.commit();
91+
connection2.commit();
92+
93+
// Verify that both transactions succeeded.
94+
connection1.setAutoCommit(true);
95+
try (ResultSet resultSet =
96+
connection1.createStatement().executeQuery("select * from test order by id")) {
97+
assertTrue(resultSet.next());
98+
assertEquals("One", resultSet.getString("value"));
99+
assertTrue(resultSet.next());
100+
assertEquals("Two", resultSet.getString("value"));
101+
assertFalse(resultSet.next());
102+
}
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)