Skip to content

Commit 59cec9b

Browse files
authored
docs: add sample for transaction timeouts (googleapis#2599)
Our current samples show how to set timeout and retry settings for all RPC invocations for a given RPC method, and how to set a timeout for a single statement, but we did not have a sample for setting a timeout for an entire transaction. This is something that customers have been asking multiple times. This sample shows how a transaction timeout can be set by using a gRPC context with a timeout.
1 parent dd3e9a0 commit 59cec9b

File tree

4 files changed

+136
-3
lines changed

4 files changed

+136
-3
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
5757
If you are using Gradle without BOM, add this to your dependencies:
5858

5959
```Groovy
60-
implementation 'com.google.cloud:google-cloud-spanner:6.45.2'
60+
implementation 'com.google.cloud:google-cloud-spanner:6.45.3'
6161
```
6262

6363
If you are using SBT, add this to your dependencies:
6464

6565
```Scala
66-
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.2"
66+
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.3"
6767
```
6868
<!-- {x-version-update-end} -->
6969

@@ -320,6 +320,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/
320320
| Statement Timeout Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java) |
321321
| Tag Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TagSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TagSample.java) |
322322
| Tracing Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TracingSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TracingSample.java) |
323+
| Transaction Timeout Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/TransactionTimeoutExample.java) |
323324
| Update Database Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseSample.java) |
324325
| Update Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateDatabaseWithDefaultLeaderSample.java) |
325326
| Update Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateInstanceConfigSample.java) |
@@ -430,7 +431,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
430431
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html
431432
[stability-image]: https://img.shields.io/badge/stability-stable-green
432433
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg
433-
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.2
434+
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.3
434435
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
435436
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
436437
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles

samples/snippets/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<groupId>com.google.cloud.samples</groupId>
1818
<artifactId>shared-configuration</artifactId>
1919
<version>1.2.0</version>
20+
<relativePath/>
2021
</parent>
2122

2223
<properties>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2023 Google Inc.
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.example.spanner;
18+
19+
// [START spanner_transaction_timeout]
20+
21+
import com.google.cloud.spanner.DatabaseClient;
22+
import com.google.cloud.spanner.DatabaseId;
23+
import com.google.cloud.spanner.ResultSet;
24+
import com.google.cloud.spanner.Spanner;
25+
import com.google.cloud.spanner.SpannerOptions;
26+
import com.google.cloud.spanner.Statement;
27+
import io.grpc.Context;
28+
import io.grpc.Context.CancellableContext;
29+
import io.grpc.Deadline;
30+
import java.util.concurrent.Executors;
31+
import java.util.concurrent.ScheduledExecutorService;
32+
import java.util.concurrent.TimeUnit;
33+
34+
/**
35+
* Sample showing how to set a timeout for an entire transaction for the Cloud Spanner Java client.
36+
*/
37+
class TransactionTimeoutExample {
38+
39+
static void executeTransactionWithTimeout() {
40+
// TODO(developer): Replace these variables before running the sample.
41+
String projectId = "my-project";
42+
String instanceId = "my-instance";
43+
String databaseId = "my-database";
44+
45+
executeTransactionWithTimeout(projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS);
46+
}
47+
48+
// Execute a read/write transaction with a timeout for the entire transaction.
49+
static void executeTransactionWithTimeout(
50+
String projectId,
51+
String instanceId,
52+
String databaseId,
53+
long timeoutValue,
54+
TimeUnit timeoutUnit) {
55+
try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build()
56+
.getService()) {
57+
DatabaseClient client =
58+
spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
59+
// Create a gRPC context with a deadline and with cancellation.
60+
// gRPC context deadlines require the use of a scheduled executor.
61+
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
62+
try (CancellableContext context =
63+
Context.current()
64+
.withDeadline(Deadline.after(timeoutValue, timeoutUnit), executor)
65+
.withCancellation()) {
66+
context.run(
67+
() -> {
68+
client
69+
.readWriteTransaction()
70+
.run(
71+
transaction -> {
72+
try (ResultSet resultSet =
73+
transaction.executeQuery(
74+
Statement.of(
75+
"SELECT SingerId, FirstName, LastName\n"
76+
+ "FROM Singers\n"
77+
+ "ORDER BY LastName, FirstName"))) {
78+
while (resultSet.next()) {
79+
System.out.printf(
80+
"%d %s %s\n",
81+
resultSet.getLong("SingerId"),
82+
resultSet.getString("FirstName"),
83+
resultSet.getString("LastName"));
84+
}
85+
}
86+
String sql =
87+
"INSERT INTO Singers (SingerId, FirstName, LastName)\n"
88+
+ "VALUES (20, 'George', 'Washington')";
89+
long rowCount = transaction.executeUpdate(Statement.of(sql));
90+
System.out.printf("%d record inserted.%n", rowCount);
91+
return null;
92+
});
93+
});
94+
}
95+
}
96+
}
97+
}
98+
// [END spanner_transaction_timeout]

samples/snippets/src/test/java/com/example/spanner/SpannerStandaloneExamplesIT.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@
1717
package com.example.spanner;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertThrows;
22+
import static org.junit.Assert.assertTrue;
2023

2124
import com.google.api.gax.longrunning.OperationFuture;
2225
import com.google.cloud.spanner.DatabaseAdminClient;
2326
import com.google.cloud.spanner.DatabaseClient;
2427
import com.google.cloud.spanner.DatabaseId;
28+
import com.google.cloud.spanner.ErrorCode;
2529
import com.google.cloud.spanner.Instance;
2630
import com.google.cloud.spanner.KeySet;
2731
import com.google.cloud.spanner.Mutation;
2832
import com.google.cloud.spanner.Spanner;
33+
import com.google.cloud.spanner.SpannerException;
2934
import com.google.cloud.spanner.SpannerOptions;
3035
import com.google.cloud.spanner.Value;
3136
import com.google.common.collect.ImmutableList;
@@ -36,6 +41,7 @@
3641
import java.util.Collections;
3742
import java.util.Iterator;
3843
import java.util.concurrent.ExecutionException;
44+
import java.util.concurrent.TimeUnit;
3945
import org.junit.AfterClass;
4046
import org.junit.Before;
4147
import org.junit.BeforeClass;
@@ -133,6 +139,33 @@ public void executeSqlWithTimeout_shouldWriteData() {
133139
assertThat(out).contains("1 record inserted.");
134140
}
135141

142+
@Test
143+
public void testTransactionWithTimeout_shouldWriteData() {
144+
String projectId = spanner.getOptions().getProjectId();
145+
String out =
146+
runExample(
147+
() ->
148+
TransactionTimeoutExample.executeTransactionWithTimeout(
149+
projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS));
150+
assertTrue(out, out.contains("1 record inserted"));
151+
}
152+
153+
@Test
154+
public void testTransactionWithTimeout_shouldFailWithDeadlineExceeded() {
155+
String projectId = spanner.getOptions().getProjectId();
156+
// Execute a transaction with a 5 millisecond timeout. The transaction executes both a read, a
157+
// write, and a commit operation. Each of these would normally take at least 5 milliseconds.
158+
SpannerException exception =
159+
assertThrows(
160+
SpannerException.class,
161+
() ->
162+
runExample(
163+
() ->
164+
TransactionTimeoutExample.executeTransactionWithTimeout(
165+
projectId, instanceId, databaseId, 5L, TimeUnit.MILLISECONDS)));
166+
assertEquals(ErrorCode.DEADLINE_EXCEEDED, exception.getErrorCode());
167+
}
168+
136169
@Test
137170
public void addNumericColumn_shouldSuccessfullyAddColumn()
138171
throws InterruptedException, ExecutionException {

0 commit comments

Comments
 (0)