如何在NestJs与Sequelize中管理多数据库连接(复用、关闭)?
NestJS + Sequelize 实现动态多租户数据库连接(连接复用+请求路由)
核心方案概述
通过JWT解析获取用户所属公司ID生成对应数据库名称;用连接池缓存复用已创建的Sequelize实例,避免重复初始化;通过NestJS请求拦截器将当前请求对应的连接绑定到请求上下文,确保每个请求路由到正确的数据库;同时实现连接的自动回收与闲置关闭。
实现步骤
1. 连接池管理工具:缓存已创建的Sequelize实例
创建单例工具类,统一管理所有租户的数据库连接,避免重复创建:
import { Sequelize } from 'sequelize-typescript'; import config from './config'; // 你的配置文件 // 缓存已创建的Sequelize实例 const connectionCache = new Map<string, Sequelize>(); export class TenantConnectionManager { // 获取或创建租户连接 static async getConnection(dbname: string): Promise<Sequelize> { const dbKey = `company_${dbname}`; // 缓存存在则直接返回 if (connectionCache.has(dbKey)) { return connectionCache.get(dbKey)!; } // 缓存不存在则创建新连接 const sequelize = new Sequelize(dbKey, config().database.user, config().database.pass, { dialect: 'mysql', port: config().database.port, replication: { write: { host: config().database.host, port: config().database.port, }, read: [{ host: config().database.hostRead, port: config().database.port, }] }, pool: { max: 50, idle: 30000, // 闲置30秒自动释放连接池内的连接 acquire: 60000, // 获取连接超时时间 }, define: { timestamps: false, }, }); // 注册模型 sequelize.addModels([/* 你的实体类集合 */]); // 验证连接(生产环境建议关闭自动sync,改用手动迁移) await sequelize.authenticate(); // await sequelize.sync(); // 加入缓存 connectionCache.set(dbKey, sequelize); // 闲置超过1小时自动回收整个连接实例 sequelize.on('idle', () => { setTimeout(() => { if (sequelize.connectionManager.pool.count === 0) { sequelize.close(); connectionCache.delete(dbKey); } }, 3600000); }); return sequelize; } // 手动关闭指定租户连接 static async closeConnection(dbname: string): Promise<void> { const dbKey = `company_${dbname}`; const sequelize = connectionCache.get(dbKey); if (sequelize) { await sequelize.close(); connectionCache.delete(dbKey); } } }
2. 请求拦截器:绑定租户连接到请求上下文
编写拦截器,从JWT解析公司ID,获取对应连接并绑定到请求对象:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { TenantConnectionManager } from './tenant-connection-manager'; @Injectable() export class TenantConnectionInterceptor implements NestInterceptor { async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { const request = context.switchToHttp().getRequest(); // 从JWT解析后的用户信息中获取公司ID(假设已通过AuthGuard解析到request.user) const companyId = request.user.companyId; if (!companyId) { throw new Error('请求中缺少公司ID'); } // 获取租户连接并绑定到请求上下文 const sequelize = await TenantConnectionManager.getConnection(companyId); request.tenantConnection = sequelize; return next.handle(); } }
3. 动态模型提供者:注入当前请求的连接
自定义模型提供者,让服务自动获取当前请求对应的租户连接:
import { Provider } from '@nestjs/common'; import { getModelToken } from '@nestjs/sequelize'; import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'; // 生成动态模型提供者的工具函数 export function createTenantModelProvider(model: any): Provider { return { provide: getModelToken(model), useFactory: (context: ExecutionContextHost) => { const request = context.switchToHttp().getRequest(); // 从请求上下文获取租户连接,返回对应模型实例 const sequelize = request.tenantConnection; return sequelize.getRepository(model); }, inject: [ExecutionContextHost], }; }
在业务模块中注册提供者:
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { TenantConnectionInterceptor } from './tenant-connection.interceptor'; import { User } from './entities/user.entity'; import { createTenantModelProvider } from './tenant-model.provider'; import { UserService } from './user.service'; import { UserController } from './user.controller'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: TenantConnectionInterceptor, }, createTenantModelProvider(User), UserService, ], controllers: [UserController], }) export class UserModule {}
4. 服务中使用动态模型
直接注入模型即可自动使用当前租户的数据库连接:
import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { User } from './entities/user.entity'; @Injectable() export class UserService { constructor( @InjectModel(User) private readonly userModel: typeof User, ) {} async findAll(): Promise<User[]> { // 自动使用当前租户的数据库连接查询 return this.userModel.findAll(); } }
关键问题解决
- 连接复用:通过
connectionCache缓存已创建的Sequelize实例,相同租户的请求直接复用,避免重复初始化开销。 - 请求路由:拦截器将租户连接绑定到请求上下文,确保每个请求使用对应租户的数据库。
- 连接回收:利用Sequelize连接池的
idle事件,结合超时逻辑自动回收闲置过久的连接;同时提供手动关闭接口,适配租户注销等场景。
内容的提问来源于stack exchange,提问作者ssSilas_s
相关产品推荐
相关产品推荐

