🗄️ Database

Transaction Management

Handle database transactions for data consistency.

TypeORM Transactions

Using QueryRunner

async transferFunds(fromId: string, toId: string, amount: number) {
  const queryRunner = this.dataSource.createQueryRunner();
  await queryRunner.connect();
  await queryRunner.startTransaction();

  try {
    await queryRunner.manager.decrement(Account, { id: fromId }, 'balance', amount);
    await queryRunner.manager.increment(Account, { id: toId }, 'balance', amount);

    await queryRunner.commitTransaction();
  } catch (err) {
    await queryRunner.rollbackTransaction();
    throw err;
  } finally {
    await queryRunner.release();
  }
}

Using @Transaction Decorator

@Transaction()
async createInvoiceWithItems(
  @TransactionManager() manager: EntityManager,
  dto: CreateInvoiceDTO
) {
  const invoice = await manager.save(Invoice, dto);
  const items = dto.items.map(item => ({
    ...item,
    invoiceId: invoice.id,
  }));
  await manager.save(InvoiceItem, items);
  return invoice;
}

Using EntityManager

await this.dataSource.transaction(async (manager) => {
  const employee = await manager.save(Employee, employeeData);
  const user = await manager.save(User, {
    ...userData,
    employeeId: employee.id,
  });
  return { employee, user };
});

Transaction Isolation Levels

LevelDirty ReadNon-RepeatablePhantom
READ UNCOMMITTED✅✅✅
READ COMMITTED❌✅✅
REPEATABLE READ❌❌✅
SERIALIZABLE❌❌❌
await this.dataSource.transaction("SERIALIZABLE", async (manager) => {
  // Strictest isolation
});

Best Practices

  • Keep transactions short
  • Avoid long-running queries inside transactions
  • Handle deadlocks with retry logic
  • Use the lowest isolation level that ensures correctness

Edit this page

Copyright © 2023-Present Ever Co. LTD.Docs by Sourcey