Interface Transaction
-
public interface Transaction
A JDBC transaction coordinator.An instance of this type represents a potential transaction or an active transaction at any given time. The
begin()
method must be called on a transaction to actually initiate a transaction.This type is effectively an asynchronous adapter to the JDBC
Connection
class's transactional methods such asConnection.commit()
,Connection.setSavepoint()
,Connection.rollback()
etc.It also (optionally) manages an execution global binding, analogous to thread local globals with synchronous frameworks such as Spring's transaction management. This allows implicit use of the “current” transaction's connection (see
current()
andconnection()
).Transaction objects are reusable, but cannot be used concurrently.
import org.h2.jdbcx.JdbcDataSource; import ratpack.exec.Blocking; import ratpack.exec.Operation; import ratpack.func.Block; import ratpack.jdbctx.Transaction; import ratpack.test.exec.ExecHarness; import ratpack.func.Exceptions; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; public class Example { private static DataSource txDs; private static Transaction tx; public static void main(String[] args) throws Exception { JdbcDataSource ds = new JdbcDataSource(); ds.setURL("jdbc:h2:mem:transactionExamples;DB_CLOSE_DELAY=-1"); txDs = Transaction.dataSource(ds); tx = Transaction.create(ds::getConnection); try (Connection connection = txDs.getConnection()) { try (Statement statement = connection.createStatement()) { statement.executeUpdate("CREATE TABLE `tbl` (`value` VARCHAR(50))"); } } List<Block> examples = Arrays.asList( Example::singleTransactionExample, Example::singleTransactionRollbackExample, Example::nestedTransactionExample, Example::nestedTransactionRollbackExample, () -> manualTransactionExample(true), () -> manualTransactionExample(false) ); try (ExecHarness harness = ExecHarness.harness()) { for (Block example : examples) { harness.execute(Operation.of(example)); reset(); } } } private static void reset() throws SQLException { try (Connection connection = txDs.getConnection()) { connection.createStatement().execute("DELETE FROM tbl"); } } private static Operation insert(String value) { return Blocking.op(() -> { try (Connection connection = txDs.getConnection()) { connection.createStatement().execute("INSERT INTO `tbl` (`value`) VALUES (" + value + ")"); } }); } private static Block assertValues(String... expected) { return () -> Blocking.get(() -> { try (Connection connection = txDs.getConnection()) { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT `value` FROM `tbl`;"); List<String> actual = new ArrayList<>(); while (resultSet.next()) { actual.add(resultSet.getString(1)); } return actual; } }) .then(actual -> assertEquals(Arrays.asList(expected), actual)); } // BEGIN EXAMPLES private static void singleTransactionExample() { tx.wrap(insert("1")).then(assertValues("1")); } private static void singleTransactionRollbackExample() { RuntimeException exception = new RuntimeException("1"); tx.wrap( insert("1") .next(() -> { throw exception; }) ) .onError(e -> { assertSame(e, exception); Operation.of(assertValues()).then(); }) .then(() -> { throw new IllegalStateException("operation should have failed"); }); } private static void nestedTransactionExample() { tx.wrap( insert("1") .next(tx.wrap(insert("2"))) ) .then(assertValues("1", "2")); } private static void nestedTransactionRollbackExample() { RuntimeException exception = new RuntimeException("1"); tx.wrap( insert("1") .next( tx.wrap( insert("2") .next(() -> { throw exception; }) ) // recover from the error, and insert something else .mapError(e -> insert("3").then()) ) ) .then(assertValues("1", "3")); } private static void manualTransactionExample(boolean fail) { tx.begin() .next(insert("1")) .next(() -> { if (fail) { throw new RuntimeException("!"); } }) .onError(e -> tx.rollback().then(() -> { throw Exceptions.toException(e); }) ) .next(tx.commit()) .onError(e -> assertValues().map(Operation::of).then()) .then(assertValues("1")); } }
- Since:
- 1.5
-
-
Method Summary
All Methods Static Methods Instance Methods Abstract Methods Default Methods Modifier and Type Method Description Transaction
autoBind(boolean autoBind)
Sets the auto binding behaviour of the transaction.Operation
begin()
Starts a transaction.default Transaction
bind()
Binds this transaction to the current execution.static Transaction
bound(Factory<? extends Connection> connectionFactory)
Creates a transaction implementation that delegates to the execution bound transaction.Operation
commit()
Commits the transaction, or pops the most recent savepoint off the stack.static Optional<Connection>
connection()
The connection of the current transaction if it is active.static Transaction
create(Factory<? extends Connection> connectionFactory)
Creates a new transaction.static Optional<Transaction>
current()
The current execution bound transaction, if any.static DataSource
dataSource(DataSource dataSource)
Decorates the given data source to beTransaction
aware.static Transaction
get(Factory<? extends Connection> connectionFactory)
Returns the current transaction if present, otherwise a newly created transaction.Optional<Connection>
getConnection()
The underlying connection for the transaction.default boolean
isActive()
Whether this transaction is active.boolean
isAutoBind()
Whether this transaction is auto-binding.static Transaction
join()
Returns the current transaction if present.Operation
rollback()
Initiates a transaction rollback.default boolean
unbind()
Unbinds this transaction from the current execution.Operation
wrap(Operation operation)
Decorates the given operation in a transaction boundary.<T> Promise<T>
wrap(Promise<T> promise)
Decorates the given promise in a transaction boundary.default <T> Promise<T>
wrap(Factory<? extends Promise<T>> promiseFactory)
Executes the given factory and yields the resultant promise in a transaction.
-
-
-
Method Detail
-
dataSource
static DataSource dataSource(DataSource dataSource)
Decorates the given data source to beTransaction
aware.This method can be used to create a data source that implicitly uses the connection of the active transaction if there is one. This is a typical pattern in an application that interacts with a single database. More complex applications may require more explicit connection assignment.
If a connection is requested while there is a current active transaction, its connection will be returned. The returned connection is effectively un-closeable. It will be closed when the overarching transaction is completed.
If a connection is requested while there is NOT a current active transaction, a connection from
dataSource
will be returned.All other methods/functions always delegate to the given
dataSource
.- Parameters:
dataSource
- the data source to delegate to- Returns:
- a connection aware data source
-
current
static Optional<Transaction> current()
The current execution bound transaction, if any.When a transaction is active (i.e.
begin()
has been called), the instance is bound to the current execution. This behaviour can be disabled viaautoBind(boolean)
.- Returns:
- the current execution bound transaction, if any.
-
connection
static Optional<Connection> connection()
The connection of the current transaction if it is active.- Returns:
- the connection of the current transaction if it is active
-
create
static Transaction create(Factory<? extends Connection> connectionFactory)
Creates a new transaction.This method always creates a new transaction. It is more typical to use
get(Factory)
to use the existing transaction, or create a new one if none exists.- Parameters:
connectionFactory
- the connection factory- Returns:
- the newly created transaction
-
get
static Transaction get(Factory<? extends Connection> connectionFactory)
Returns the current transaction if present, otherwise a newly created transaction.- Parameters:
connectionFactory
- the connection factory- Returns:
- the current transaction if present, otherwise a newly created transaction
-
bound
static Transaction bound(Factory<? extends Connection> connectionFactory)
Creates a transaction implementation that delegates to the execution bound transaction.This transaction can be used as an application wide singleton. When any transaction method is called, it will delegate to the
current()
transaction if there is one, or it willcreate(Factory)
a new one.This differs to
get(Factory)
in that this method returns a dynamically delegating transaction, instead of an actual transaction.Typically, this method can be used to create a single
Transaction
object that is used throughout the application.- Parameters:
connectionFactory
- the connection factory- Returns:
- a transaction object that delegates to the current transaction, or creates a new one, for each method
-
join
static Transaction join() throws TransactionException
Returns the current transaction if present.- Returns:
- the current transaction if present
- Throws:
TransactionException
- if there is no bound transaction
-
bind
default Transaction bind() throws TransactionException
Binds this transaction to the current execution.The instance is added to the current execution's registry.
It is typically not necessary to call this directly. Transactions default to “auto binding”. That is, this method is called implicitly when the transaction starts.
- Returns:
this
- Throws:
TransactionException
- if a different transaction is bound to the execution
-
unbind
default boolean unbind()
Unbinds this transaction from the current execution.If the transaction is not bound, this method is effectively a noop and returns false.
- Returns:
- whether this transaction was actually bound
- Throws:
TransactionException
- if a different transaction is bound to the execution- See Also:
bind()
-
isActive
default boolean isActive()
Whether this transaction is active.- Returns:
- whether this transaction is active
-
getConnection
Optional<Connection> getConnection()
The underlying connection for the transaction.The optional will be empty if the transaction is not active.
- Returns:
- the underlying connection for the transaction
-
begin
Operation begin()
Starts a transaction.If the transaction is not active, a new connection will be acquired. If a transaction has already begun, creates a new savepoint and adds it to an internal stack.
A call to this method MUST be paired with a subsequent call to either
commit()
orrollback()
. Generally, it is more convenient to usewrap(Promise)
orwrap(Operation)
which manages this.- Returns:
- an operation to start a transaction or create a savepoint
-
rollback
Operation rollback()
Initiates a transaction rollback.If the transaction is not active, the operation will fail with
TransactionException
.If the save point stack is empty (i.e. there are no nested transactions), A
Connection.rollback()
is issued and the underlying connection is closed. The transaction will no longer be active and will beunbound
(if auto-binding).If the save point stack is NOT empty, the most recent savepoint is rolled back. The underlying connection will not be closed, and the transaction will remain active and
bound
(if auto-binding).- Returns:
- an operation that rolls back the transaction or to the most recent savepoint
-
commit
Operation commit()
Commits the transaction, or pops the most recent savepoint off the stack.If the transaction is not active, the operation will fail with
TransactionException
.If the save point stack is empty (i.e. there are no nested transactions), A
Connection.commit()
()} is issued and the underlying connection is closed. The transaction will no longer be active and will beunbound
(if auto-binding).If the save point stack is NOT empty, the most recent savepoint is popped from the stack. The underlying connection will not be closed, and the transaction will remain active and
bound
(if auto-binding).- Returns:
- an operation that commits the transaction or pops the most recent save point
-
autoBind
Transaction autoBind(boolean autoBind)
Sets the auto binding behaviour of the transaction.An auto-binding transaction will implicitly call
bind()
when becoming active, andunbind()
when closing.It generally only helps to disable auto binding if multiple connections are used within the same execution.
Defaults to
true
.- Parameters:
autoBind
- whether to enable auto-binding- Returns:
this
-
isAutoBind
boolean isAutoBind()
Whether this transaction is auto-binding.- Returns:
- whether this transaction is auto-binding
-
wrap
<T> Promise<T> wrap(Promise<T> promise)
Decorates the given promise in a transaction boundary.The decoration effectively calls
begin()
before yielding the promise. If it fails,rollback()
will be issued. If it succeeds,commit()
will be issued.- Type Parameters:
T
- the type of promised value- Parameters:
promise
- the promise to yield in a transaction- Returns:
- a promise that will yield within a transaction
-
wrap
default <T> Promise<T> wrap(Factory<? extends Promise<T>> promiseFactory)
Executes the given factory and yields the resultant promise in a transaction.- Type Parameters:
T
- the type of promised value- Parameters:
promiseFactory
- the factory of the promise to yield in a transaction- Returns:
- a promise that will yield within a transaction
-
wrap
Operation wrap(Operation operation)
Decorates the given operation in a transaction boundary.The decoration effectively calls
begin()
before yielding the operation. If it fails,rollback()
will be issued. If it succeeds,commit()
will be issued.- Parameters:
operation
- the operation to yield in a transaction- Returns:
- a operation that will yield within a transaction
-
-