概述
本节为进阶内容,聚焦 TypeORM 模块的两个关键优化:
- DataSource 实例复用 -- 通过
dataSourceFactory和全局 Map 缓存已创建的 DataSource,避免重复创建连接池 - 连接生命周期管理 -- 利用 NestJS 的
OnApplicationShutdown钩子,在应用关闭时销毁所有数据库连接
为什么需要管理 DataSource 实例
TypeORM 的 DataSource 内部维护了连接池。每次通过 new DataSource(options) 创建实例时,即使 options 不同(如不同类型的数据库),也会产生独立的连接池:
options1 (MySQL) -> new DataSource() -> DS1 (连接池 1)
options2 (PostgreSQL) -> new DataSource() -> DS2 (连接池 2)
text
如果不加以管理,随着请求中不同 tenantId 的出现,会不断创建新的 DataSource 和连接池,最终导致内存溢出。
官方 dataSourceFactory 机制
TypeORM 官方模块提供了 dataSourceFactory 工厂方法,用于自定义 DataSource 的创建过程:
// 官方接口定义
interface TypeOrmModuleOptions {
useFactory?: (...args: any[]) => Promise<DataSourceOptions>;
dataSourceFactory?: (
options: DataSourceOptions,
) => Promise<DataSource> | DataSource;
}
typescript
useFactory-- 产生 DataSource 的配置选项dataSourceFactory-- 接收配置选项,实际创建并返回 DataSource 实例
创建 TypeORM ConfigService
将数据库配置逻辑从 AppService 和 AppModule 中抽离到独立的 TypeOrmConfigService:
// database/typeorm/typeorm-config.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { ConfigService } from '@nestjs/config';
import { TypeOrmOptionsFactory } from '@nestjs/typeorm';
import { DataSourceOptions } from 'typeorm';
@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
constructor(
@Inject(REQUEST) private readonly request: any,
private readonly configService: ConfigService,
) {}
createTypeOrmOptions(): DataSourceOptions {
const tenantId = this.request.headers['tenant-id'] as string;
// 默认配置来自 .env
const defaultConfig = {
type: this.configService.get('DB_TYPE'),
host: this.configService.get('DB_HOST'),
port: parseInt(this.configService.get('DB_PORT'), 10) as any,
username: this.configService.get('DB_USERNAME'),
password: this.configService.get('DB_PASSWORD'),
database: this.configService.get('DB_DATABASE'),
synchronize: true,
autoLoadEntities: true,
};
// 根据 tenantId 动态覆盖配置
if (tenantId === 'postgres') {
return {
...defaultConfig,
type: 'postgres',
port: 5432,
username: 'pg_user',
password: 'example',
} as DataSourceOptions;
}
return defaultConfig as DataSourceOptions;
}
}
typescript
AppModule 简化
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
useClass: TypeOrmConfigService,
dataSourceFactory: (options) => {
// 这里管理 DataSource 的创建与缓存
},
}),
],
})
export class AppModule {}
typescript
通过 useClass 直接引用 TypeOrmConfigService,代码更加简洁。
DataSource 实例缓存
使用 Map 缓存已创建的 DataSource 实例,以 tenantId 作为 key:
// app.module.ts
const connections = new Map<string, DataSource>();
TypeOrmModule.forRootAsync({
useClass: TypeOrmConfigService,
dataSourceFactory: async (options: any) => {
const tenantId = options.tenantId || 'default';
// 命中缓存,直接返回已有实例
if (connections.has(tenantId)) {
console.log('reuse', tenantId);
return connections.get(tenantId)!;
}
// 创建新的 DataSource
const dataSource = new DataSource(options);
await dataSource.initialize();
console.log('new DataSource', tenantId);
// 缓存实例
connections.set(tenantId, dataSource);
return dataSource;
},
});
typescript
验证复用效果
发送多次请求观察控制台日志:
请求 tenant-id: mysql -> 打印 "new DataSource default"
请求 tenant-id: postgres -> 打印 "new DataSource postgres"
请求 tenant-id: mysql -> 打印 "reuse default"(复用已有实例)
text
连接销毁与生命周期管理
开启 Shutdown Hooks
在 main.ts 中启用应用关闭钩子:
// main.ts
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // 必须开启
typescript
实现 OnApplicationShutdown
在 AppService 中实现 OnApplicationShutdown 接口,并在应用关闭时销毁所有连接:
// app.service.ts
import { Injectable, Inject, OnApplicationShutdown } from '@nestjs/common';
import { DataSource } from 'typeorm';
@Injectable()
export class AppService implements OnApplicationShutdown {
constructor(
@Inject('TYPEORM_CONNECTIONS')
private readonly connections: Map<string, DataSource>,
) {}
async onApplicationShutdown(signal?: string) {
if (this.connections.size > 0) {
for (const key of Array.from(this.connections.keys())) {
const dataSource = this.connections.get(key)!;
await dataSource.destroy();
console.log('destroyed', key);
}
}
}
}
typescript
注册 Provider
在 AppModule 中注册 connections 的 Provider,以便依赖注入:
// app.module.ts
providers: [
AppService,
{
provide: 'TYPEORM_CONNECTIONS',
useValue: connections,
},
],
typescript
验证销毁流程
- 发送几次请求,建立数据库连接
- 按
Ctrl+C停止应用 - 观察控制台输出
destroyed <key>,确认连接已正确断开
架构示意
┌─────────────────────┐
│ AppModule │
│ connections: Map │
└────────┬────────────┘
|
┌──────────────┼──────────────┐
| | |
DataSource DataSource DataSource
(default) (postgres) (mysql)
[cached] [cached] [cached]
| | |
连接池 1 连接池 2 连接池 3
OnApplicationShutdown
|
遍历 connections -> dataSource.destroy()
text
关键要点
- DataSource 与连接池的关系:每个
new DataSource(options)都会创建独立的连接池,不同数据库类型的 options 必然产生不同的 DataSource 实例 - Map 缓存策略:以 tenantId 为 key 缓存 DataSource,避免相同配置的重复创建
- 生命周期钩子:
enableShutdownHooks()是onApplicationShutdown回调生效的前提 - 代码分层:将配置逻辑抽离到
TypeOrmConfigService,业务逻辑保留在AppService,职责清晰
↑