Using Transactions
Last updated
Last updated
Node-Boot supports transactions trough a @Transactional()
decorator that benefits from the auto-configurations provided by the @node-boot/starter-persistence starter package.
Transactions propagation across the request chain is ensured by leveraging Node.js Async Hooks capabilities.
In order to use transactions, you have to have the persistence layer active and configured by installing the @node-boot/starter-persistence package and configuring it in order to use your preferred database:
After persistence layer is configured and working as expected, you can start decorating controller or service functions as transactional.
Every service method that needs to be transactional, need to use the @Transactional()
decorator.
The decorator can take an optional propagation
as argument to define the propagation behaviour.
The decorator can take an optional isolationLevel
as argument to define the
isolation level
(by default it will use your database driver's default isolation level)
You can also use DataSource
and EntityManager
objects together with repositories in transactions:
The following propagation options can be specified:
MANDATORY
- Support a current transaction, throw an exception if none exists.
NESTED
- Execute within a nested transaction if a current transaction exists, behave like REQUIRED
else.
NEVER
- Execute non-transactionally, throw an exception if a transaction exists.
NOT_SUPPORTED
- Execute non-transactionally, suspend the current transaction if one exists.
REQUIRED
(default behaviour) - Support a current transaction, create a new one if none exists.
REQUIRES_NEW
- Create a new transaction, and suspend the current transaction if one exists.
SUPPORTS
- Support a current transaction, execute non-transactionally if none exists.
The following isolation level options can be specified:
READ_UNCOMMITTED
- A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur.
READ_COMMITTED
- A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur.
REPEATABLE_READ
- A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur.
SERIALIZABLE
= A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented.
Note: If a transaction already exist and a method is decorated with @Transactional
and propagation
does not equal to REQUIRES_NEW
, then the declared isolationLevel
value will not be taken into account.
Because you hand over control of the transaction creation to this library, there is no way for you to know whether or not the current transaction was successfully persisted to the database.
To circumvent that, we expose three helper methods that allow you to hook into the transaction lifecycle and take appropriate action after a commit/rollback.
runOnTransactionCommit(cb)
takes a callback to be executed after the current transaction was successfully committed
runOnTransactionRollback(cb)
takes a callback to be executed after the current transaction rolls back. The callback gets the error that initiated the rollback as a parameter.
runOnTransactionComplete(cb)
takes a callback to be executed at the completion of the current transactional context. If there was an error, it gets passed as an argument.
storageDriver
- Determines which underlying mechanism (like Async Local Storage or cls-hooked) the library should use for handling and propagating transactions. By default, it's StorageDriver.AUTO
.
maxHookHandlers
- Controls how many hooks (commit
, rollback
, complete
) can be used simultaneously. If you exceed the number of hooks of same type, you get a warning. This is a useful to find possible memory leaks. You can set this options to 0
or Infinity
to indicate an unlimited number of listeners. By default, it's 10
.
isolationLevel
- isolation level for transactional context (isolation levels)
propagation
- propagation behaviour's for nest transactional contexts (propagation behaviors)
Option that determines which underlying mechanism the library should use for handling and propagating transactions.
The possible variants:
AUTO
- Automatically selects the appropriate storage mechanism based on the Node.js version, using AsyncLocalStorage
for Node.js versions 16 and above, and defaulting to cls-hooked
for earlier versions.
CLS_HOOKED
- Utilizes the cls-hooked
package to provide context storage, supporting both legacy Node.js versions with AsyncWrap for versions below 8.2.1, and using async_hooks
for later versions.
ASYNC_LOCAL_STORAGE
- Uses the built-in AsyncLocalStorage
feature, available from Node.js version 16 onwards,
If no storage driver config is provided, the library is configured with Auto mode.
Just in case you are not a fun of decorators, you can make use of the following utility functions to handle transactions:
Run code in transactional context.
Wrap function in transactional context