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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:55:18