nl.topicus.jdbc.transaction.CloudSpannerTransaction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spanner-jdbc Show documentation
Show all versions of spanner-jdbc Show documentation
JDBC Driver for Google Cloud Spanner
package nl.topicus.jdbc.transaction;
import java.sql.SQLException;
import nl.topicus.jdbc.shaded.com.google.cloud.Timestamp;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.DatabaseClient;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Key;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.KeySet;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Mutation;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Options.QueryOption;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Options.ReadOption;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.ReadOnlyTransaction;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.ResultSet;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Statement;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Struct;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.TransactionContext;
import nl.topicus.jdbc.shaded.com.google.rpc.Code;
import nl.topicus.jdbc.CloudSpannerConnection;
import nl.topicus.jdbc.exception.CloudSpannerSQLException;
/**
* An abstraction of transactions on Google Cloud Spanner JDBC connections.
*
* @author loite
*
*/
public class CloudSpannerTransaction implements TransactionContext
{
public static class TransactionException extends RuntimeException
{
private static final long serialVersionUID = 1L;
private TransactionException(String message, SQLException cause)
{
super(message, cause);
}
}
private TransactionThread transactionThread;
private ReadOnlyTransaction readOnlyTransaction;
private DatabaseClient dbClient;
private CloudSpannerConnection connection;
public CloudSpannerTransaction(DatabaseClient dbClient, CloudSpannerConnection connection)
{
this.dbClient = dbClient;
this.connection = connection;
}
public boolean isRunning()
{
return readOnlyTransaction != null || transactionThread != null;
}
public boolean hasBufferedMutations()
{
return transactionThread != null && transactionThread.hasBufferedMutations();
}
public void begin() throws SQLException
{
if (connection.isReadOnly())
{
if (readOnlyTransaction == null)
{
readOnlyTransaction = dbClient.readOnlyTransaction();
}
}
else
{
if (transactionThread == null)
{
transactionThread = new TransactionThread(dbClient);
transactionThread.start();
}
}
}
public Timestamp commit() throws SQLException
{
Timestamp res = null;
try
{
if (connection.isReadOnly())
{
if (readOnlyTransaction != null)
{
readOnlyTransaction.close();
}
}
else
{
if (transactionThread != null)
{
res = transactionThread.commit();
}
}
}
finally
{
transactionThread = null;
readOnlyTransaction = null;
}
return res;
}
public void rollback() throws SQLException
{
try
{
if (connection.isReadOnly())
{
if (readOnlyTransaction != null)
{
readOnlyTransaction.close();
}
}
else
{
if (transactionThread != null)
{
transactionThread.rollback();
}
}
}
finally
{
transactionThread = null;
readOnlyTransaction = null;
}
}
@FunctionalInterface
private static interface TransactionAction
{
public void apply(String xid) throws SQLException;
}
public void prepareTransaction(String xid) throws SQLException
{
checkTransaction();
preparedTransactionAction(xid, transactionThread::prepareTransaction);
}
public void commitPreparedTransaction(String xid) throws SQLException
{
checkTransaction();
preparedTransactionAction(xid, transactionThread::commitPreparedTransaction);
}
public void rollbackPreparedTransaction(String xid) throws SQLException
{
checkTransaction();
preparedTransactionAction(xid, transactionThread::rollbackPreparedTransaction);
}
private void preparedTransactionAction(String xid, TransactionAction action) throws SQLException
{
try
{
if (connection.isReadOnly())
{
throw new CloudSpannerSQLException(
"Connection is in read-only mode and cannot be used for prepared transactions",
Code.FAILED_PRECONDITION);
}
else
{
action.apply(xid);
}
}
finally
{
transactionThread = null;
readOnlyTransaction = null;
}
}
private void checkTransaction()
{
if (transactionThread == null && readOnlyTransaction == null)
{
try
{
begin();
}
catch (SQLException e)
{
throw new TransactionException("Failed to start new transaction", e);
}
}
}
@Override
public void buffer(Mutation mutation)
{
checkTransaction();
if (transactionThread == null)
throw new IllegalStateException("Mutations are not allowed in read-only mode");
transactionThread.buffer(mutation);
}
@Override
public void buffer(Iterable mutations)
{
checkTransaction();
if (transactionThread == null)
throw new IllegalStateException("Mutations are not allowed in read-only mode");
transactionThread.buffer(mutations);
}
@Override
public ResultSet executeQuery(Statement statement, QueryOption... options)
{
checkTransaction();
if (readOnlyTransaction != null)
return readOnlyTransaction.executeQuery(statement, options);
else if (transactionThread != null)
return transactionThread.executeQuery(statement);
throw new IllegalStateException("No transaction found (this should not happen)");
}
@Override
public ResultSet read(String table, KeySet keys, Iterable columns, ReadOption... options)
{
return null;
}
@Override
public ResultSet readUsingIndex(String table, String index, KeySet keys, Iterable columns,
ReadOption... options)
{
return null;
}
@Override
public Struct readRow(String table, Key key, Iterable columns)
{
return null;
}
@Override
public Struct readRowUsingIndex(String table, String index, Key key, Iterable columns)
{
return null;
}
@Override
public ResultSet analyzeQuery(Statement statement, QueryAnalyzeMode queryMode)
{
return null;
}
/**
* Close method is needed for the interface, but does not do anything
*/
@Override
public void close()
{
// no-op as there is nothing to close or throw away
}
}