@Transactional
Annotate methods with @Transactional
and all database queries and changes
will occur in a single transaction.
The transaction will use the default database, be put into the "thread local scope" and will commit if the methods completes successfully.
@Transactional
public void process(OffsetDateTime startOffset) {
...
customer.save();
contact.save();
}
Transaction.current()
Use Transaction.current()
to return the current transaction from "thread local scope"
of the default database.
@Transactional
public void process(OffsetDateTime startOffset) {
...
Transaction txn = Transaction.current();
...
}
use database.currentTransaction()
to return the current transaction for a given database
that is typically not the default database.
Database otherDb = DB.byName("other");
Transaction txn = otherDb.currentTransaction();
beginTransaction()
As an alternative to @Transactional we can use beginTransaction()
to
start an explicit transaction. The transaction is put into the "thread local scope" and used
by any subsequent queries, save, delete etc.
We should use a try with resources block to ensure that the transaction is closed if any error occurs in the block.
try (Transaction transaction = DB.beginTransaction()) {
// do stuff...
Customer customer = ...
customer.save();
Order order = ...
order.save();
transaction.commit();
}
Kotlin transaction.use { }
Kotlin use
is used similarly to try with resources to ensure
the transaction is closed.
DB.beginTransaction().use { transaction ->
// do stuff...
val customer = ...
customer.save();
val order = ...
order.save();
transaction.commit()
}
commit(), rollback(), end()
Often with a try with resources block we don't need explicit calls to
transaction.rollback()
or transaction.end()
like the
example for beginTransaction() above.
rollback()
will rollback the transaction and commonly we use that
in a catch block.
end()
will rollback the transaction if it has not already been committed.
We primarily use end()
in a finally block.
Transaction transaction = DB.beginTransaction()
try {
// do stuff...
Customer customer = ...
customer.save();
Order order = ...
order.save();
transaction.commit();
} catch (SomeException e) {
transaction.rollback();
} finally {
transaction.end(); // rollback if not committed
}
commitAndContinue()
Occasionally in larger transactions we get to a point where we would like
to commit the changes to this point but then carry on processing with
subsequent changes potentially failing.
We use transaction.commitAndContinue()
for this.
setRollbackOnly()
We use transaction.setRollbackOnly()
such that a transaction
will not commit but only rollback.
We can make use of this when some processing has a sort of "preview" mode where we process changes but then do not commit. This can also be useful in some testing scenarios.
createTransaction()
database.createTransaction()
will create a transaction but it will
NOT be put in "thread local scope". We generally use this when we have
a transaction that we want to pass between threads.
For example: start a transaction, obtain a row lock, if that is successful pass the transaction to a task to execute via a background thread.
When we use createTransaction()
we must explicitly use the transaction
in queries and saving etc. We explicitly specify the transaction with query beans
via ... and model.save(transaction) etc.
try (Transaction transaction = DB.getDefault().createTransaction()) {
...
var customer = new QCustomer(transaction) // explicit transaction
.name.eq("Rob")
.findOne();
...
customer.update(transaction); // explicit transaction
}
Implicit transactions
When no transactions are specified explicitly Ebean will create a transaction to perform the action.
Query - read only transaction
For queries Ebean will look to use a read only transaction. If a Read Only DataSource
has
be configured Ebean will look to use that by default.
Insert, Update, Delete
For all persist requests like save, insert, update, delete Ebean will create a transaction and perform
a COMMIT
at the end of the operation.