Interface DatabaseClient

public interface DatabaseClient

Interface for all the APIs that are used to read/write data into a Cloud Spanner database. An instance of this is tied to a specific database.

Methods

executePartitionedUpdate(Statement stmt, Options.UpdateOption[] options)

public abstract long executePartitionedUpdate(Statement stmt, Options.UpdateOption[] options)

Returns the lower bound of rows modified by this DML statement.

The method will block until the update is complete. Running a DML statement with this method does not offer exactly once semantics, and therefore the DML statement should be idempotent. The DML statement must be fully-partitionable. Specifically, the statement must be expressible as the union of many statements which each access only a single row of the table. This is a Partitioned DML transaction in which a single Partitioned DML statement is executed. Partitioned DML partitions the key space and runs the DML statement over each partition in parallel using separate, internal transactions that commit independently. Partitioned DML transactions do not need to be committed.

Partitioned DML updates are used to execute a single DML statement with a different execution strategy that provides different, and often better, scalability properties for large, table-wide operations than DML in a #readWriteTransaction() transaction. Smaller scoped statements, such as an OLTP workload, should prefer using TransactionContext#executeUpdate(Statement) with #readWriteTransaction().

That said, Partitioned DML is not a drop-in replacement for standard DML used in #readWriteTransaction().

  • The DML statement must be fully-partitionable. Specifically, the statement must be expressible as the union of many statements which each access only a single row of the table.
  • The statement is not applied atomically to all rows of the table. Rather, the statement is applied atomically to partitions of the table, in independent internal transactions. Secondary index rows are updated atomically with the base table rows.
  • Partitioned DML does not guarantee exactly-once execution semantics against a partition. The statement will be applied at least once to each partition. It is strongly recommended that the DML statement should be idempotent to avoid unexpected results. For instance, it is potentially dangerous to run a statement such as UPDATE table SET column = column + 1 as it could be run multiple times against some rows.
  • The partitions are committed automatically - there is no support for Commit or Rollback. If the call returns an error, or if the client issuing the DML statement dies, it is possible that some rows had the statement executed on them successfully. It is also possible that statement was never executed against other rows.
  • If any error is encountered during the execution of the partitioned DML operation (for instance, a UNIQUE INDEX violation, division by zero, or a value that cannot be stored due to schema constraints), then the operation is stopped at that point and an error is returned. It is possible that at this point, some partitions have been committed (or even committed multiple times), and other partitions have not been run at all.

Given the above, Partitioned DML is good fit for large, database-wide, operations that are idempotent, such as deleting old rows from a very large table.

Parameters
NameDescription
stmtStatement
optionsUpdateOption[]
Returns
TypeDescription
long

readOnlyTransaction()

public abstract ReadOnlyTransaction readOnlyTransaction()

Returns a read-only transaction context in which a multiple reads and/or queries can be performed using TimestampBound#strong() concurrency. All reads/queries will use the same timestamp, and the timestamp can be inspected after any read/query has returned data or finished successfully.

Example of read only transaction.


 long singerId = my_singer_id;
 long albumId = my_album_id;
 String singerColumn = "FirstName";
 String albumColumn = "AlbumTitle";
 String albumTitle = null;
 // ReadOnlyTransaction should be closed to prevent resource leak.
 try (ReadOnlyTransaction txn = dbClient.readOnlyTransaction()) {
   Struct singerRow =
       txn.readRow("Singers", Key.of(singerId), Collections.singleton(singerColumn));
   Struct albumRow =
       txn.readRow("Albums", Key.of(singerId, albumId), Collections.singleton(albumColumn));
   singerRow.getString(singerColumn);
   albumTitle = albumRow.getString(albumColumn);
 }
 
Returns
TypeDescription
ReadOnlyTransaction

readOnlyTransaction(TimestampBound bound)

public abstract ReadOnlyTransaction readOnlyTransaction(TimestampBound bound)

Returns a read-only transaction context in which a multiple reads and/or queries can be performed at the given timestamp bound. All reads/queries will use the same timestamp, and the timestamp can be inspected after any read/query has returned data or finished successfully.

Note that the bounded staleness modes, TimestampBound.Mode#MIN_READ_TIMESTAMP and TimestampBound.Mode#MAX_STALENESS, are not supported for multi-use read-only transactions.

Example of read only transaction with timestamp bound.


 long singerId = my_singer_id;
 long albumId = my_album_id;
 String singerColumn = "FirstName";
 String albumColumn = "AlbumTitle";
 String albumTitle = null;
 // ReadOnlyTransaction should be closed to prevent resource leak.
 try (ReadOnlyTransaction txn =
     dbClient.readOnlyTransaction(TimestampBound.ofExactStaleness(10, TimeUnit.SECONDS))) {
   Struct singerRow =
       txn.readRow("Singers", Key.of(singerId), Collections.singleton(singerColumn));
   Struct albumRow =
       txn.readRow("Albums", Key.of(singerId, albumId), Collections.singleton(albumColumn));
   singerRow.getString(singerColumn);
   albumTitle = albumRow.getString(albumColumn);
 }
 
Parameter
NameDescription
boundTimestampBound

the timestamp bound at which to perform the read

Returns
TypeDescription
ReadOnlyTransaction

readWriteTransaction(Options.TransactionOption[] options)

public abstract TransactionRunner readWriteTransaction(Options.TransactionOption[] options)

Returns a transaction runner for executing a single logical transaction with retries. The returned runner can only be used once.

Example of a read write transaction.

 
 long singerId = my_singer_id;
 TransactionRunner runner = dbClient.readWriteTransaction();
 runner.run(
     new TransactionCallable<Void>() {

       @Override
       public Void run(TransactionContext transaction) throws Exception {
         String column = "FirstName";
         Struct row =
             transaction.readRow("Singers", Key.of(singerId), Collections.singleton(column));
         String name = row.getString(column);
         transaction.buffer(
             Mutation.newUpdateBuilder("Singers").set(column).to(name.toUpperCase()).build());
         return null;
       }
     });
 

Options for a transaction can include:

Parameter
NameDescription
optionsTransactionOption[]
Returns
TypeDescription
TransactionRunner

runAsync(Options.TransactionOption[] options)

public abstract AsyncRunner runAsync(Options.TransactionOption[] options)

Returns an asynchronous transaction runner for executing a single logical transaction with retries. The returned runner can only be used once.

Example of a read write transaction.

 
 Executor executor = Executors.newSingleThreadExecutor();
 final long singerId = my_singer_id;
 AsyncRunner runner = client.runAsync();
 ApiFuture

Options for a transaction can include:

Parameter
NameDescription
optionsTransactionOption[]
Returns
TypeDescription
AsyncRunner

singleUse()

public abstract ReadContext singleUse()

Returns a context in which a single read can be performed using TimestampBound#strong() concurrency. This method will return a ReadContext that will not return the read timestamp that was used by Cloud Spanner. If you want to be able to access the read timestamp, you should use the method #singleUseReadOnlyTransaction().

Example of single use.


 long singerId = my_singer_id;
 String column = "FirstName";
 Struct row =
     dbClient.singleUse().readRow("Singers", Key.of(singerId), Collections.singleton(column));
 String firstName = row.getString(column);
 
Returns
TypeDescription
ReadContext

singleUse(TimestampBound bound)

public abstract ReadContext singleUse(TimestampBound bound)

Returns a context in which a single read can be performed at the given timestamp bound. This method will return a ReadContext that will not return the read timestamp that was used by Cloud Spanner. If you want to be able to access the read timestamp, you should use the method #singleUseReadOnlyTransaction().

Example of single use with timestamp bound.


 long singerId = my_singer_id;
 String column = "FirstName";
 Struct row = dbClient.singleUse(TimestampBound.ofMaxStaleness(10, TimeUnit.SECONDS))
     .readRow("Singers", Key.of(singerId), Collections.singleton(column));
 String firstName = row.getString(column);
 
Parameter
NameDescription
boundTimestampBound

the timestamp bound at which to perform the read

Returns
TypeDescription
ReadContext

singleUseReadOnlyTransaction()

public abstract ReadOnlyTransaction singleUseReadOnlyTransaction()

Returns a read-only transaction context in which a single read or query can be performed using TimestampBound#strong() concurrency. This method differs from #singleUse() in that the read timestamp used may be inspected after the read has returned data or finished successfully.

Example of single use read only transaction.


 long singerId = my_singer_id;
 String column = "FirstName";
 ReadOnlyTransaction txn = dbClient.singleUseReadOnlyTransaction();
 Struct row = txn.readRow("Singers", Key.of(singerId), Collections.singleton(column));
 row.getString(column);
 Timestamp timestamp = txn.getReadTimestamp();
 
Returns
TypeDescription
ReadOnlyTransaction

singleUseReadOnlyTransaction(TimestampBound bound)

public abstract ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound)

Returns a read-only transaction context in which a single read or query can be performed at given timestamp bound. This method differs from #singleUse(TimestampBound) in that the read timestamp used may be inspected after the read has returned data or finished successfully.

Example of single use read only transaction with timestamp bound.


 long singerId = my_singer_id;
 String column = "FirstName";
 ReadOnlyTransaction txn =
     dbClient.singleUseReadOnlyTransaction(TimestampBound.ofMaxStaleness(10, TimeUnit.SECONDS));
 Struct row = txn.readRow("Singers", Key.of(singerId), Collections.singleton(column));
 row.getString(column);
 Timestamp timestamp = txn.getReadTimestamp();
 
Parameter
NameDescription
boundTimestampBound

the timestamp bound at which to perform the read

Returns
TypeDescription
ReadOnlyTransaction

transactionManager(Options.TransactionOption[] options)

public abstract TransactionManager transactionManager(Options.TransactionOption[] options)

Returns a transaction manager which allows manual management of transaction lifecycle. This API is meant for advanced users. Most users should instead use the #readWriteTransaction() API instead.

Example of using TransactionManager.


 long singerId = my_singer_id;
 try (TransactionManager manager = dbClient.transactionManager()) {
   TransactionContext transaction = manager.begin();
   while (true) {
     String column = "FirstName";
     Struct row = transaction.readRow("Singers", Key.of(singerId), Collections.singleton(column));
     String name = row.getString(column);
     transaction.buffer(
         Mutation.newUpdateBuilder("Singers").set(column).to(name.toUpperCase()).build());
     try {
       manager.commit();
       break;
     } catch (AbortedException e) {
       Thread.sleep(e.getRetryDelayInMillis());
       transaction = manager.resetForRetry();
     }
   }
 }
 

Options for a transaction can include:

Parameter
NameDescription
optionsTransactionOption[]
Returns
TypeDescription
TransactionManager

transactionManagerAsync(Options.TransactionOption[] options)

public abstract AsyncTransactionManager transactionManagerAsync(Options.TransactionOption[] options)

Returns an asynchronous transaction manager which allows manual management of transaction lifecycle. This API is meant for advanced users. Most users should instead use the #runAsync() API instead.

Example of using AsyncTransactionManager.


 long singerId = 1L;
 try (AsyncTransactionManager manager = client.transactionManagerAsync()) {
   TransactionContextFuture transactionFuture = manager.beginAsync();
   while (true) {
     String column = "FirstName";
     CommitTimestampFuture commitTimestamp =
         transactionFuture
             .then(
                 (transaction, __) ->
                     transaction.readRowAsync(
                         "Singers", Key.of(singerId), Collections.singleton(column)))
             .then(
                 (transaction, row) -> {
                   String name = row.getString(column);
                   return transaction.bufferAsync(
                       Mutation.newUpdateBuilder("Singers")
                           .set(column)
                           .to(name.toUpperCase())
                           .build());
                 })
             .commitAsync();
     try {
       commitTimestamp.get();
       break;
     } catch (AbortedException e) {
       Thread.sleep(e.getRetryDelayInMillis());
       transactionFuture = manager.resetForRetryAsync();
     }
   }
 }
 

Options for a transaction can include:

Options for a transaction can include:

Parameter
NameDescription
optionsTransactionOption[]
Returns
TypeDescription
AsyncTransactionManager

write(Iterable<Mutation> mutations)

public abstract Timestamp write(Iterable<Mutation> mutations)

Writes the given mutations atomically to the database.

This method uses retries and replay protection internally, which means that the mutations are applied exactly once on success, or not at all if an error is returned, regardless of any failures in the underlying network. Note that if the call is cancelled or reaches deadline, it is not possible to know whether the mutations were applied without performing a subsequent database operation, but the mutations will have been applied at most once.

Example of blind write.


 long singerId = my_singer_id;
 Mutation mutation = Mutation.newInsertBuilder("Singer")
         .set("SingerId")
         .to(singerId)
         .set("FirstName")
         .to("Billy")
         .set("LastName")
         .to("Joel")
         .build();
 dbClient.write(Collections.singletonList(mutation));
 
Parameter
NameDescription
mutationsIterable<Mutation>
Returns
TypeDescription
com.google.cloud.Timestamp

the timestamp at which the write was committed

Exceptions
TypeDescription
SpannerException

writeAtLeastOnce(Iterable<Mutation> mutations)

public abstract Timestamp writeAtLeastOnce(Iterable<Mutation> mutations)

Writes the given mutations atomically to the database without replay protection.

Since this method does not feature replay protection, it may attempt to apply mutations more than once; if the mutations are not idempotent, this may lead to a failure being reported when the mutation was applied once. For example, an insert may fail with ErrorCode#ALREADY_EXISTS even though the row did not exist before this method was called. For this reason, most users of the library will prefer to use #write(Iterable) instead. However, writeAtLeastOnce() requires only a single RPC, whereas write() requires two RPCs (one of which may be performed in advance), and so this method may be appropriate for latency sensitive and/or high throughput blind writing.

Example of unprotected blind write.


 long singerId = my_singer_id;
 Mutation mutation = Mutation.newInsertBuilder("Singers")
         .set("SingerId")
         .to(singerId)
         .set("FirstName")
         .to("Billy")
         .set("LastName")
         .to("Joel")
         .build();
 dbClient.writeAtLeastOnce(Collections.singletonList(mutation));
 
Parameter
NameDescription
mutationsIterable<Mutation>
Returns
TypeDescription
com.google.cloud.Timestamp

the timestamp at which the write was committed

Exceptions
TypeDescription
SpannerException

writeAtLeastOnceWithOptions(Iterable<Mutation> mutations, Options.TransactionOption[] options)

public abstract CommitResponse writeAtLeastOnceWithOptions(Iterable<Mutation> mutations, Options.TransactionOption[] options)

Writes the given mutations atomically to the database without replay protection.

Since this method does not feature replay protection, it may attempt to apply mutations more than once; if the mutations are not idempotent, this may lead to a failure being reported when the mutation was applied once. For example, an insert may fail with ErrorCode#ALREADY_EXISTS even though the row did not exist before this method was called. For this reason, most users of the library will prefer to use #write(Iterable) instead. However, writeAtLeastOnce() requires only a single RPC, whereas write() requires two RPCs (one of which may be performed in advance), and so this method may be appropriate for latency sensitive and/or high throughput blind writing.

Example of unprotected blind write.


 long singerId = my_singer_id;
 Mutation mutation = Mutation.newInsertBuilder("Singers")
         .set("SingerId")
         .to(singerId)
         .set("FirstName")
         .to("Billy")
         .set("LastName")
         .to("Joel")
         .build();
 dbClient.writeAtLeastOnceWithOptions(
         Collections.singletonList(mutation),
         Options.priority(RpcPriority.LOW));
 

Options for a transaction can include:

Parameters
NameDescription
mutationsIterable<Mutation>
optionsTransactionOption[]
Returns
TypeDescription
CommitResponse

a response with the timestamp at which the write was committed

Exceptions
TypeDescription
SpannerException

writeWithOptions(Iterable<Mutation> mutations, Options.TransactionOption[] options)

public abstract CommitResponse writeWithOptions(Iterable<Mutation> mutations, Options.TransactionOption[] options)

Writes the given mutations atomically to the database with the given options.

This method uses retries and replay protection internally, which means that the mutations are applied exactly once on success, or not at all if an error is returned, regardless of any failures in the underlying network. Note that if the call is cancelled or reaches deadline, it is not possible to know whether the mutations were applied without performing a subsequent database operation, but the mutations will have been applied at most once.

Example of blind write.


 long singerId = my_singer_id;
 Mutation mutation = Mutation.newInsertBuilder("Singer")
         .set("SingerId")
         .to(singerId)
         .set("FirstName")
         .to("Billy")
         .set("LastName")
         .to("Joel")
         .build();
 dbClient.writeWithOptions(
         Collections.singletonList(mutation),
         Options.priority(RpcPriority.HIGH));
 

Options for a transaction can include:

Parameters
NameDescription
mutationsIterable<Mutation>
optionsTransactionOption[]
Returns
TypeDescription
CommitResponse

a response with the timestamp at which the write was committed

Exceptions
TypeDescription
SpannerException