Transactional API usage in LoopBack 4

Overview

A transaction is a sequence of data operations performed as a single logicalunit of work. Many relational databases support transactions to help enforcedata consistency and business logic requirements.

A repository can perform operations in a transaction when the backing datasourceis attached to one of the following connectors:

Transaction APIs

The @loopback/repository package includes TransactionalRepository interfacebased on EntityCrudRepository interface. The TransactionalRepositoryinterface adds a beginTransaction() API that, for connectors that allow it,will start a new Transaction. The beginTransaction() function gives access tothe lower-level transaction API, leaving it up to the user to create and managetransaction objects, commit them on success or roll them back at the end of allintended operations. See Handling Transactions belowfor more details.

Handling Transactions

Seethe API reference forfull transaction lower-level API documentation.

Performing operations in a transaction typically involves the following steps:

  • Start a new transaction.
  • Perform create, read, update, and delete operations in the transaction.
  • Commit or rollback the transaction.

Start transaction

Use the beginTransaction() method to start a new transaction from a repositoryclass using DefaultTransactionalRepository as a base class.

Here is an example:

  1. import {
  2. Transaction,
  3. DefaultTransactionalRepository,
  4. IsolationLevel,
  5. } from '@loopback/repository';
  6. // assuming there is a Note model extending Entity class, and
  7. // ds datasource which is backed by a transaction enabled
  8. // connector
  9. const repo = new DefaultTransactionalRepository(Note, ds);
  10. // Now we have a transaction (tx)
  11. const tx = await repo.beginTransaction(IsolationLevel.READ_COMMITTED);

You can also extend DefaultTransactionalRepository for custom classes:

  1. import {inject} from '@loopback/core';
  2. import {
  3. juggler,
  4. Transaction,
  5. DefaultTransactionalRepository,
  6. IsolationLevel,
  7. } from '@loopback/repository';
  8. import {Note, NoteRelations} from '../models';
  9. export class NoteRepository extends DefaultTransactionalRepository<
  10. Note,
  11. typeof Note.prototype.id,
  12. NoteRelations
  13. > {
  14. constructor(@inject('datasources.ds') ds: juggler.DataSource) {
  15. super(Note, ds);
  16. }
  17. }

Isolation levels

When you call beginTransaction(), you can optionally specify a transactionisolation level. LoopBack transactions support the following isolation levels:

  • Transaction.READ_UNCOMMITTED
  • Transaction.READ_COMMITTED (default)
  • Transaction.REPEATABLE_READ
  • Transaction.SERIALIZABLEIf you don’t specify an isolation level, the transaction uses READ_COMMITTED .

Important:

Oracle only supports READ_COMMITTED and SERIALIZABLE.

For more information about database-specific isolation levels, see:

Perform operations in a transaction

To perform create, retrieve, update, and delete operations in the transaction,add the transaction object to the Options parameter of the standard create(),update(),deleteAll()(and so on) methods.

For example, again assuming a Note model, repo transactional repository, andtransaction object tx created as demonstrated inStart transaction section:

  1. const created = await repo.create({title: 'Groceries'}, {transaction: tx});
  2. const updated = await repo.update(
  3. {title: 'Errands', id: created.id},
  4. {transaction: tx},
  5. );
  6. // commit the transaction to persist the changes
  7. await tx.commit();

Propagating a transaction is explicit by passing the transaction object via theoptions argument for all create, retrieve, update, and delete and relationmethods.

Commit or rollback

Transactions allow you either to commit the transaction and persist the CRUDbehaviour onto the database or rollback the changes. The two methods availableon transaction objects are as follows:

  1. /**
  2. * Commit the transaction
  3. */
  4. commit(): Promise<void>;
  5. /**
  6. * Rollback the transaction
  7. */
  8. rollback(): Promise<void>;

Set up timeout

You can specify a timeout (in milliseconds) to begin a transaction. If atransaction is not finished (committed or rolled back) before the timeout, itwill be automatically rolled back upon timeout by default.

For example, again assuming a Note model and repo transactional repository,the timeout can be specified as part of the Options object passed into thebeginTransaction method.

  1. const tx: Transaction = await repo.beginTransaction({
  2. isolationLevel: IsolationLevel.READ_COMMITTED,
  3. timeout: 30000, // 30000ms = 30s
  4. });

Avoid long waits or deadlocks

Please be aware that a transaction with certain isolation level will lockdatabase objects. Performing multiple methods within a transactionasynchronously has the great potential to block other transactions (explicit orimplicit). To avoid long waits or even deadlocks, you should:

  • Keep the transaction as short-lived as possible
  • Don’t serialize execution of methods across multiple transactions