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

TypeDI按请求注入数据库连接:线程安全与最优实现咨询

关于TypeDI+Routing-Controllers多数据库请求注入的问题解答

并行请求下现有方案的问题

两种现有实现方式都会导致Prisma实例混用,原因如下:

  • 方式一(调用.initPrisma传入实例):TypeDI默认服务为单例模式,多个并行请求会同时修改同一个服务实例的Prisma属性,后发起的请求会覆盖先发起请求的实例,导致数据查询串库。
  • 方式二(Container.set全局设置实例):Container.set操作的是全局容器,所有请求共享同一个容器实例,并行请求会互相覆盖全局的PrismaService,必然导致实例混用。

更优的依赖注入方案:请求作用域+Vhost中间件

利用TypeDI的**请求作用域(Request Scope)**结合Routing-Controllers的请求上下文,为每个请求注入对应Vhost的Prisma实例,完全避免并行请求冲突。具体实现步骤如下:

1. 配置容器与路由控制器

确保Routing-Controllers使用TypeDI作为依赖容器:

import { useContainer, createExpressServer } from 'routing-controllers';
import { Container } from 'typedi';

useContainer(Container);

const app = createExpressServer({
  controllers: [__dirname + '/controllers/*.ts'],
  middlewares: [__dirname + '/middlewares/*.ts'],
});

app.listen(3000);

2. 实现Prisma实例工厂(复用实例)

创建工厂类负责根据Vhost创建/复用PrismaClient实例,避免重复初始化:

import { Service } from 'typedi';
import { PrismaClient } from '@prisma/client';

@Service()
export class PrismaFactory {
  // 缓存已创建的Prisma实例,按Vhost区分
  private instanceCache = new Map<string, PrismaClient>();

  getInstance(vhost: string): PrismaClient {
    if (!this.instanceCache.has(vhost)) {
      const dbUrl = this.getDbUrlByVhost(vhost);
      const prisma = new PrismaClient({
        datasources: {
          db: { url: dbUrl },
        },
      });
      this.instanceCache.set(vhost, prisma);
    }
    return this.instanceCache.get(vhost)!;
  }

  // 根据Vhost匹配对应数据库连接串(可从配置文件/环境变量读取)
  private getDbUrlByVhost(vhost: string): string {
    const dbConfig = {
      'shopA.example.com': 'postgresql://user:pass@db-shopA:5432/shopA',
      'shopB.example.com': 'postgresql://user:pass@db-shopB:5432/shopB',
      'default.example.com': 'postgresql://user:pass@db-default:5432/default',
    };
    return dbConfig[vhost] || dbConfig['default.example.com'];
  }
}

3. 编写Vhost中间件注入请求作用域实例

在请求进入控制器前,解析Vhost并将对应Prisma实例注入当前请求的子容器:

import { Middleware, ExpressMiddlewareInterface } from 'routing-controllers';
import { Container } from 'typedi';
import { PrismaFactory } from '../services/PrismaFactory';

@Middleware({ type: 'before' })
export class VhostDbMiddleware implements ExpressMiddlewareInterface {
  use(req: any, res: any, next: () => void): void {
    // 解析请求的Vhost(去除端口号)
    const vhost = req.headers.host?.split(':')[0] || 'default.example.com';
    
    // 从全局容器获取工厂实例,创建对应Vhost的PrismaClient
    const prismaFactory = Container.get(PrismaFactory);
    const prisma = prismaFactory.getInstance(vhost);
    
    // 将实例注入当前请求的子容器,标记为请求作用域
    Container.of(req).set('PrismaClient', prisma);
    
    next();
  }
}

4. 服务与控制器中注入使用

在业务服务中直接注入请求作用域的Prisma实例:

import { Service } from 'typedi';
import { Inject } from 'typedi';
import { PrismaClient } from '@prisma/client';

@Service()
export class OrderService {
  constructor(@Inject('PrismaClient') private prisma: PrismaClient) {}

  async getOrderById(orderId: number) {
    return this.prisma.order.findUnique({ where: { id: orderId } });
  }
}

控制器中注入服务即可正常使用:

import { Controller, Get, Param } from 'routing-controllers';
import { Inject } from 'typedi';
import { OrderService } from '../services/OrderService';

@Controller('/orders')
export class OrderController {
  constructor(@Inject() private orderService: OrderService) {}

  @Get('/:id')
  async getOrder(@Param('id') id: number) {
    return this.orderService.getOrderById(id);
  }
}

方案优势

  • 无实例混用:每个请求拥有独立的子容器,注入的Prisma实例仅属于当前请求,并行请求完全隔离。
  • 性能优化:通过工厂类缓存Prisma实例,避免每个请求重复初始化数据库连接。
  • 代码解耦:业务服务无需关心数据库选择逻辑,仅依赖注入的Prisma实例即可。

内容的提问来源于stack exchange,提问作者Leandro Costa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:12:43