You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 15:14:37