在实际业务开发中,尤其是涉及到资金转账、库存修改等需要数据一致性的场景,事务是我们保障系统稳定性的重要手段。本文将通过一个账户余额转账的例子,来讲解 NestJS + TypeORM 中事务的使用方式,并与非事务的处理方式进行对比。
我们先定义一个简单的实体类,用于表示账户信息:
import { Entity, Column, PrimaryGeneratedColumn } from typeorm;
// 测试事务一致性 - 账户
@Entity('custom_account')
export class CustromEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ default: 0 })
balance: number;
}
async transfer(fromId: number, toId: number, amount: number): Promise<void> {
await this.custromRepository.manager.transaction(async (transactionalEntityManager) => {
const fromAccount = await transactionalEntityManager.findOneByOrFail(CustromEntity, { id: fromId });
if (fromAccount.balance < amount) {
throw new Error('Insufficient funds');
}
fromAccount.balance -= amount;
await transactionalEntityManager.save(fromAccount);
// 模拟中间可能出现的异常
if (Math.random() > 0.5) {
throw new Error('Network error occurred');
}
const toAccount = await transactionalEntityManager.findOneByOrFail(CustromEntity, { id: toId });
toAccount.balance += amount;
await transactionalEntityManager.save(toAccount);
});
}
说明:
manager.transaction()
执行数据库操作。async unsafeTransfer(fromId: number, toId: number, amount: number): Promise<void> {
const fromAccount = await this.custromRepository.findOneByOrFail({ id: fromId });
if (fromAccount.balance < amount) {
throw new Error('Insufficient funds');
}
fromAccount.balance -= amount;
await this.custromRepository.save(fromAccount);
// 模拟可能出现的错误
if (Math.random() > 0.5) {
throw new Error('Network error occurred');
}
const toAccount = await this.custromRepository.findOneByOrFail({ id: toId });
toAccount.balance += amount;
await this.custromRepository.save(toAccount);
}
风险点:
场景 | 是否使用事务 | 数据一致性 | 报错回滚 |
---|---|---|---|
使用事务转账 | ✅ 是 | ✅ 一致 | ✅ 支持 |
不使用事务转账 | ❌ 否 | ❌ 不一致 | ❌ 不支持 |
结论:在涉及多步数据库操作时,强烈建议使用事务来保证数据一致性。
transactionalEntityManager
处理所有涉及的数据表操作,避免直接操作 Repository。typeorm-transactional
是一个基于 CLS(Continuation Local Storage) 的库,它允许你在使用 TypeORM 时,通过装饰器的方式声明事务,并且能够在多个 service 之间自动传播事务上下文。
它的目标是:消除手动传递 EntityManager 的繁琐代码,提升事务处理的可维护性与可读性。
特性 | 描述 |
---|---|
✅ 装饰器支持 | 使用 @Transactional() 装饰器标注方法自动开启事务 |
✅ 自动事务传播 | 支持 service 间方法调用,事务上下文自动共享 |
✅ 兼容 TypeORM | 与 NestJS + TypeORM 无缝集成 |
✅ 支持异步调用 | 基于 async_hooks 提供异步上下文隔离 |
npm install typeorm-transactional-cls-hooked
在 main.ts
中初始化 CLS:
import { initializeTransactionalContext } from 'typeorm-transactional-cls-hooked';
async function bootstrap() {
initializeTransactionalContext(); // 初始化 CLS 上下文
await app.listen(3000);
}
import { Transactional } from 'typeorm-transactional-cls-hooked';
@Injectable()
export class AccountService {
constructor(private readonly accountRepo: Repository<AccountEntity>) {}
@Transactional()
async transfer(fromId: number, toId: number, amount: number) {
const from = await this.accountRepo.findOneByOrFail({ id: fromId });
const to = await this.accountRepo.findOneByOrFail({ id: toId });
from.balance -= amount;
to.balance += amount;
await this.accountRepo.save(from);
await this.accountRepo.save(to);
}
}
@Injectable()
export class LoggerService {
@Transactional()
async logTransfer(message: string) {
// 此操作会复用 AccountService 中开启的事务
await this.logRepo.save({ message });
}
}
import { getEntityManagerOrTransactionManager } from 'typeorm-transactional-cls-hooked';
const manager = getEntityManagerOrTransactionManager();
await manager.save(...);
@InjectRepository()
注入的。Promise.all
并发操作。场景 | 说明 |
---|---|
多 Service 嵌套调用 | 自动共享事务上下文 |
想简化事务逻辑的维护 | 使用装饰器声明即可 |
跨模块、异步方法中保证事务一致性 | 基于 CLS 持续跟踪 |
文章标题:NestJS 中使用 TypeORM 事务的实践与对比
文章作者:Cling.
文章链接:[复制]
最后修改时间:2025年 08月 02日 11时12分
商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。 本文采用CC BY-NC-SA 4.0进行许可。
Copyright © 2023-2025
豫ICP备2022014268号-1
「每想拥抱你一次,天空飘落一片雪,至此雪花拥抱撒哈拉!」
本站已经艰难运行了661天