如何实现NestJS的@Transactional()装饰器以在失败时回滚数据库变更
实现NestJS事务装饰器@Transactional()
要实现一个在方法执行失败时自动回滚数据库变更的@Transactional()装饰器,我们可以基于TypeORM的事务机制构建,以下是具体实现步骤:
1. 创建Transactional装饰器
在src/decorators/transactional.decorator.ts中编写装饰器代码:
import { DataSource } from 'typeorm'; export function Transactional() { return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { const dataSource: DataSource = target.dataSource; const queryRunner = dataSource.createQueryRunner(); try { await queryRunner.connect(); await queryRunner.startTransaction(); // 将事务专属EntityManager附加到服务实例 const originalManager = this.manager; this.manager = queryRunner.manager; // 执行原方法逻辑 const result = await originalMethod.apply(this, args); await queryRunner.commitTransaction(); return result; } catch (error) { await queryRunner.rollbackTransaction(); throw error; } finally { await queryRunner.release(); this.manager = originalManager; } }; return descriptor; }; }
2. 修改服务类,注入DataSource并使用装饰器
确保服务类注入TypeORM的DataSource,并且方法内使用事务专属的EntityManager操作数据库(避免使用默认Repository的全局EntityManager):
import { Inject, Injectable } from '@nestjs/common'; import { DataSource, Repository } from 'typeorm'; import { Brand } from './brand.entity'; import { BRANDS_REPOSITORY } from './brands.repository'; import { USERS_REPOSITORY } from '../users/users.repository'; import { User } from '../users/user.entity'; import { DATA_SOURCE } from '../database/database.providers'; import { Transactional } from '../../decorators/transactional.decorator'; @Injectable() export class BrandsService { constructor( @Inject(BRANDS_REPOSITORY) private readonly brandsRepository: Repository<Brand>, @Inject(USERS_REPOSITORY) private readonly usersRepository: Repository<User>, @Inject(DATA_SOURCE) private readonly dataSource: DataSource, ) {} @Transactional() async create(user: User, name: string) { // 使用事务内的EntityManager获取Repository const transactionalBrandRepo = this.manager.getRepository(Brand); let brand = transactionalBrandRepo.create({ name }); brand = await transactionalBrandRepo.save(brand); const transactionalUserRepo = this.manager.getRepository(User); user.permittedBrands.push(brand); await transactionalUserRepo.save(user); // 抛出错误测试回滚 throw new Error('Something went wrong'); return brand; } }
核心要点
- 事务上下文绑定:装饰器创建专属
QueryRunner并启动事务,将事务内的EntityManager附加到服务实例,确保所有数据库操作在同一事务中执行。 - 自动回滚:方法抛出错误时,装饰器立即执行事务回滚,撤销所有已完成的数据库变更。
- 资源清理:无论事务成功或失败,都会释放
QueryRunner资源,避免数据库连接泄漏。
内容的提问来源于stack exchange,提问作者ibodi
相关产品推荐
相关产品推荐

