withTransaction<R> function

Future<R> withTransaction<R>({
  1. required Future<R> action(),
  2. TransactionNesting nesting = TransactionNesting.nested,
  3. bool useTransaction = true,
  4. DbPool? dbPool,
  5. String? debugName,
})

Obtains a connection and starts a MySQL transaction.

The action is called within the scope of the transaction. When the action returns the transaction is automatically committed. If action throws any exception the transaction is rolledback.

In most cases you will want to call withTransaction at the very top of your call stack. This ensures that all db interactions occur within the one transaction. This is important because any db interactions that are performed outside of the transaction will not have visiblity of the db changes associated with the transaction until the transaction is committed.

MySQL does NOT allow nested transaction, therefore if you attempt to nest a transation all updates will occur within the same transaction.

Thre are some circumstances where you may want to control what occurs when you call withTransaction within the scope of an existing withTransaction call.

  1. you have a method that may or may not be called within the scope of an existing withTransaction call. In this case pass nesting = TransactionNesting.nested (which is the default)

If your code is called within the scope of an existing withTransaction call then it will be attached to the same Db connection and the same transaction. This is still NOT a nested MYSQL transaction and if you transaction fails the outer one will also fail.

If your code is called outside the scope of an existing withTransaction then a new Db connection will be obtained and a MySQL transaction started.

  1. you may need to start a second MySQL transaction whilst in the scope of a withTransaction call.

In this case pass TransactionNesting.detached. A new Db connection will be obtained and a new MySQL transaction will be started. You need to be careful that you don't create a live lock (two transactions viaing for the same resources).

  1. You want to ensure that you don't accidently nest a transaction.

Pass in TransactionNesting.notAllowed.

This isn't recommended simply because its hard to enforce within your code base. TransactionNesting.detached is probably what you really want to use.

useTransaction is intended for debugging purposes. By setting useTransaction any db changes are visible as soon as the occur rather than only once the transaction completes. So this option allows you to inspect the db as updates occur when running tests.

For most operations you don't provide a DbPool and the transaction obtains one by calling DbPool(). In some cases you may want to provide db connections from an alternate pool. In these cases pass a pool to dbPool.

Implementation

Future<R> withTransaction<R>(
    {required Future<R> Function() action,
    TransactionNesting nesting = TransactionNesting.nested,
    bool useTransaction = true,
    DbPool? dbPool,
    String? debugName}) async {
  final nestedTransaction = Scope.hasScopeKey(Transaction.transactionKey);

  switch (nesting) {
    case TransactionNesting.notAllowed:
      if (nestedTransaction) {
        throw NestedTransactionException('You are already in a transaction. '
            'Specify TransactionNesting.nestedTransaction');
      }
      return _runTransaction(action,
          useTransaction: useTransaction, shareDb: false, debugName: debugName);

    case TransactionNesting.detached:
      return _runTransaction(action,
          useTransaction: useTransaction, shareDb: false, debugName: debugName);

    case TransactionNesting.nested:
      return _runTransaction(action,
          useTransaction: useTransaction && !nestedTransaction,
          shareDb: nestedTransaction,
          debugName: debugName);
  }
}