NestJS结合Drizzle ORM如何实现类似Spring Boot的注解式数据库事务管理?
NestJS结合Drizzle ORM如何实现类似Spring Boot的注解式数据库事务管理?
当然可以实现!作为习惯了Spring Boot注解式事务的开发者,刚用NestJS+Drizzle时确实会觉得手动嵌套db.transaction()有点繁琐。不过NestJS本身支持AOP(面向切面编程),我们可以通过自定义装饰器+拦截器的组合,完美复刻出类似@Transactional的体验,不用再重复写事务包裹逻辑。
下面是具体的实现步骤,亲测可用:
1. 自定义@Transactional装饰器
首先创建一个标记用的装饰器,用来告诉拦截器哪些方法需要事务管理:
// src/common/decorators/transactional.decorator.ts import { SetMetadata, createParamDecorator, ExecutionContext } from '@nestjs/common'; // 用于标记事务的元数据key export const TRANSACTIONAL_KEY = 'transactional'; // 核心的事务装饰器 export const Transactional = () => SetMetadata(TRANSACTIONAL_KEY, true); // 用于在方法中注入事务实例的参数装饰器 export const Transaction = createParamDecorator( (_: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.transaction; }, );
2. 实现事务拦截器
这个拦截器是核心,它会自动识别标记了@Transactional的方法,开启事务、传递事务实例,出错时自动回滚:
// src/common/interceptors/transaction.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Observable } from 'rxjs'; import { db } from '../db/drizzle.config'; // 替换成你的Drizzle数据库实例路径 import { TRANSACTIONAL_KEY } from '../decorators/transactional.decorator'; @Injectable() export class TransactionInterceptor implements NestInterceptor { constructor(private readonly reflector: Reflector) {} async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { // 检查当前方法/类是否标记了事务注解 const isTransactional = this.reflector.getAllAndOverride<boolean>(TRANSACTIONAL_KEY, [ context.getHandler(), context.getClass(), ]); // 没有标记的话直接执行原方法 if (!isTransactional) { return next.handle(); } // 开启Drizzle事务,自动处理提交/回滚 return new Observable(observer => { db.transaction(async (tx) => { try { // 把事务实例挂载到请求对象上,方便后续通过@Transaction()注入 const request = context.switchToHttp().getRequest(); request.transaction = tx; // 执行原方法并返回结果 const result = await next.handle().toPromise(); observer.next(result); observer.complete(); } catch (error) { // 只要方法抛出异常,事务会自动回滚 observer.error(error); } }); }); } }
3. 全局注册拦截器
把拦截器注册为全局拦截器,这样所有模块的方法都能使用@Transactional注解:
// src/app.module.ts import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { TransactionInterceptor } from './common/interceptors/transaction.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: TransactionInterceptor, }, ], }) export class AppModule {}
4. 实际使用示例
现在就可以像Spring Boot那样,给需要事务的方法加注解了,完全不用手动写事务嵌套:
Controller层
// src/posts/posts.controller.ts import { Controller, Post, Body, GetUser } from '@nestjs/common'; import { Transactional, Transaction } from '../common/decorators/transactional.decorator'; import { PostService } from './posts.service'; import { CreatePostDto } from './dto/create-post.dto'; @Controller('posts') export class PostsController { constructor(private readonly postService: PostService) {} @Post() @Transactional() // 只需加这个注解,自动开启事务 async create( @GetUser('id') userId: number, @Body() createPostDto: CreatePostDto, @Transaction() tx: any, // 通过参数装饰器获取事务实例 ) { const post = await this.postService.create(userId, createPostDto, tx); await this.postService.like(post, tx); return post; } }
Service层
Service方法只需支持接收事务实例参数,默认用普通的db实例,这样非事务场景也能正常调用:
// src/posts/posts.service.ts import { Injectable } from '@nestjs/common'; import { db } from '../common/db/drizzle.config'; import { posts, likes } from '../common/db/schema'; import { CreatePostDto } from './dto/create-post.dto'; @Injectable() export class PostService { // tx参数默认用db,非事务场景直接调用即可 async create(userId: number, dto: CreatePostDto, tx = db) { const [post] = await tx.insert(posts).values({ userId, title: dto.title, content: dto.content, }).returning(); return post; } async like(post: typeof posts.$inferSelect, tx = db) { await tx.insert(likes).values({ postId: post.id, userId: post.userId, }); } }
关键说明
- 事务自动回滚:只要方法执行过程中抛出任何异常,Drizzle的事务都会自动回滚,和Spring的
@Transactional行为一致。 - 灵活度:你也可以给整个Controller类加
@Transactional,这样类里所有方法都会自动开启事务。 - 兼容性:这个方案完全兼容Drizzle的事务API,没有额外的侵入性。
备注:内容来源于stack exchange,提问作者Anoop Singh
相关产品推荐
相关产品推荐

