Nest.js中如何通过持久化提供者生命周期事件释放数据库连接
解决NestJS请求作用域持久化提供者无法关闭数据库连接的问题
我实现了一个带本地缓存的租户数据库连接提供者,用Scope.REQUEST+durable: true来复用连接,但请求作用域的提供者没法监听应用生命周期事件,调用app.close()时onModuleDestroy永远不会触发,导致数据库连接没关闭,Jest抛出警告:
Jest did not exit one second after the test run has completed.
下面是社区里常用的几种解决方法:
1. 将连接缓存与生命周期管理移到单例服务
请求作用域服务本身无法感知模块销毁事件,核心思路是把连接缓存和销毁逻辑抽离到单例服务中,请求作用域的TenancyService只负责从单例服务获取/复用连接。
实现示例:
首先创建单例的连接管理器:
import { Injectable, OnModuleDestroy } from '@nestjs/common'; import { DataSource, DataSourceOptions } from 'typeorm'; import { tenantedConfig } from '@/orm.config'; import { TenantService } from '@/modules/public/tenants/tenant.service'; @Injectable() export class TenantConnectionManager implements OnModuleDestroy { private tenantConnections: { [tenantId: string]: DataSource } = {}; constructor(private readonly tenantService: TenantService) {} async getDataSource(tenantId: string): Promise<DataSource> { if (!this.tenantConnections[tenantId]) { const { schema } = await this.tenantService.findOneByTenant(tenantId); const dataSource = new DataSource({ ...tenantedConfig, schema, } as DataSourceOptions); try { await dataSource.initialize(); } catch (error) { console.error('Failed to initialize tenant connection', error); throw error; } this.tenantConnections[tenantId] = dataSource; } return this.tenantConnections[tenantId]; } async onModuleDestroy() { await Promise.all( Object.values(this.tenantConnections).map(dataSource => dataSource.destroy()), ); console.debug('Tenant connections destroyed'); } }
然后修改原来的TenancyService,注入单例管理器:
import { Inject, Injectable, Scope } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { TenantConnectionManager } from './tenant-connection.manager'; interface ContextPayload { tenantId?: string; } @Injectable({ scope: Scope.REQUEST, durable: true }) export class TenancyService { private readonly tenantId: string; constructor( private readonly connectionManager: TenantConnectionManager, @Inject(REQUEST) private readonly requestContext: ContextPayload, ) { this.tenantId = requestContext.tenantId; } async getDataSource() { return this.connectionManager.getDataSource(this.tenantId); } }
这样单例的TenantConnectionManager能正常触发onModuleDestroy,在app.close()时自动销毁所有租户连接。
2. 在应用关闭时手动触发连接销毁
如果不想重构服务结构,可以在应用启动时监听beforeDestroy事件,或者在测试代码中手动调用销毁逻辑。
应用层面监听:
在main.ts中添加:
async function bootstrap() { const app = await NestFactory.create(AppModule); // 监听应用关闭事件,手动销毁连接 app.on('beforeDestroy', async () => { const connectionManager = app.get(TenantConnectionManager); await connectionManager.onModuleDestroy(); }); await app.listen(3000); } bootstrap();
测试层面手动处理:
在Jest测试的afterAll钩子中手动获取服务并销毁连接:
afterAll(async () => { const connectionManager = app.get(TenantConnectionManager); await connectionManager.onModuleDestroy(); await app.close(); });
3. 针对单个请求的连接清理(可选)
如果需要在请求结束后就清理连接(而非复用),可以用NestJS的OnRequestFinish钩子,这个方案不适合连接复用场景,仅作补充:
import { Injectable, Scope, OnRequestFinish } from '@nestjs/common'; @Injectable({ scope: Scope.REQUEST }) export class TenancyService implements OnRequestFinish { // ... 原有逻辑 async onRequestFinish() { const dataSource = this.tenantConnections[this.tenantId]; if (dataSource) { await dataSource.destroy(); } } }
内容的提问来源于stack exchange,提问作者Samuel Goldenbaum
相关产品推荐
相关产品推荐

