12-1 回顾ORM库多数据库配置&连接
前置知识回顾
Common模块基础组件
在NestJS项目开发中,common
模块通常用于存放项目的基础功能和通用工具。本节我们重点回顾两个核心组件:
1. Logger模块:系统日志记录与管理
日志系统是应用程序的"黑匣子",良好的日志设计可以帮助开发者快速定位问题。
核心功能实现:
// logger.service.ts
import { LoggerService } from '@nestjs/common';
export class CustomLogger implements LoggerService {
log(message: string) {
console.log(`[INFO] ${new Date().toISOString()} - ${message}`);
}
error(message: string, trace: string) {
console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, trace);
}
warn(message: string) {
console.warn(`[WARN] ${new Date().toISOString()} - ${message}`);
}
}
typescript
最佳实践:
- 日志分级:DEBUG/INFO/WARN/ERROR
- 结构化日志:推荐使用Winston或Pino等专业日志库
- 日志轮转:避免单个日志文件过大
- 敏感信息过滤:自动屏蔽密码等敏感字段
💡 生产环境建议:使用ELK(Elasticsearch+Logstash+Kibana)搭建日志分析系统
2. Config模块:统一配置管理方案
配置管理是保证应用灵活性的关键,NestJS提供了完善的配置管理方案。
基础配置实现:
// app.module.ts
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用
envFilePath: ['.env.development', '.env.production'], // 环境变量文件
}),
],
})
export class AppModule {}
typescript
进阶功能:
- 配置验证:
// config.schema.ts
import { plainToClass } from 'class-transformer';
import { IsNumber, validateSync } from 'class-validator';
class EnvironmentVariables {
@IsNumber()
PORT: number;
}
export function validate(config: Record<string, unknown>) {
const validatedConfig = plainToClass(EnvironmentVariables, config);
const errors = validateSync(validatedConfig);
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}
typescript
- 多环境配置:
# config/development.yaml
database:
host: localhost
port: 5432
# config/production.yaml
database:
host: cluster.prod.rds.amazonaws.com
port: 5432
yaml
- 配置热重载:
ConfigModule.forRoot({
reloadOnChange: true, // 监听文件变化
})
typescript
配置安全建议:
- 永远不要将敏感配置提交到代码仓库
- 使用KMS或Vault管理生产环境密钥
- 为不同环境设置不同的访问权限
💡 企业级方案:AWS Parameter Store或Azure App Configuration等云配置服务
扩展知识:模块化设计原则
- 单一职责原则:每个模块只关注一个特定功能
- 高内聚低耦合:模块内部紧密相关,模块间依赖明确
- 依赖注入:通过构造函数注入依赖项
- 接口隔离:通过抽象接口定义模块边界
通过这样的模块化设计,可以确保系统具有良好的可维护性和扩展性。下节课我们将深入探讨如何将这些基础模块与ORM系统进行集成。
ORM库支持的数据库类型
关系型数据库
1. TypeORM:主流ORM方案
TypeORM是目前Node.js生态中最成熟的关系型数据库ORM解决方案,支持多种SQL数据库:
核心特性:
- 支持Active Record和Data Mapper模式
- 自动生成数据库迁移脚本
- 强大的查询构建器
- 事务支持和连接池管理
多数据库连接配置:
// 多数据库配置示例
TypeOrmModule.forRoot({
name: 'primary',
type: 'postgres',
host: 'db1.example.com',
port: 5432,
username: 'user',
password: 'password',
database: 'primary_db',
synchronize: false, // 生产环境必须关闭
entities: [User, Product],
}),
TypeOrmModule.forRoot({
name: 'secondary',
type: 'mysql',
host: 'db2.example.com',
port: 3306,
username: 'user',
password: 'password',
database: 'secondary_db',
entities: [Order, Inventory],
})
typescript
最佳实践:
- 生产环境关闭
synchronize
,使用迁移脚本 - 使用连接池优化性能:
extra: { max: 20, // 最大连接数 connectionTimeoutMillis: 5000, }
typescript - 实现读写分离:
replication: { master: { host: 'master.db.com' }, slaves: [{ host: 'slave1.db.com' }, { host: 'slave2.db.com' }] }
typescript
💡 最新动态:TypeORM 0.3.x版本改进了事务管理和TypeScript支持
2. Prisma:现代类型安全ORM
Prisma是新一代的ORM工具,提供更完善的类型安全和开发体验:
核心优势:
- 自动生成的类型安全客户端
- 直观的数据建模语言
- 内置数据库迁移系统
- 强大的可视化工具
基础配置:
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
typescript
与NestJS集成:
// prisma.service.ts
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
typescript
性能对比:
特性 | TypeORM | Prisma |
---|---|---|
类型安全 | 中等 | 优秀 |
学习曲线 | 平缓 | 较陡 |
社区支持 | 强大 | 成长中 |
复杂查询能力 | 强 | 中等 |
非关系型数据库
1. MongoDB:文档型数据库
MongoDB是领先的NoSQL数据库,特别适合:
- 非结构化数据存储
- 快速迭代的开发场景
- 高吞吐量应用
NestJS集成方案:
// 基础连接
MongooseModule.forRoot('mongodb://localhost/nest', {
connectionName: 'primary',
maxPoolSize: 50, // 连接池大小
});
// 多数据库连接
MongooseModule.forRoot('mongodb://cluster1.example.com', {
connectionName: 'reports',
dbName: 'analytics',
});
typescript
2. Mongoose:MongoDB官方ORM
高级特性:
- 模式验证:
@Schema()
export class User {
@Prop({ required: true, unique: true })
email: string;
@Prop({ minlength: 6 })
password: string;
}
typescript
- 中间件支持:
userSchema.pre('save', function(next) {
if (this.isModified('password')) {
this.password = hash(this.password);
}
next();
});
typescript
- 聚合管道:
const results = await this.userModel.aggregate([
{ $match: { age: { $gt: 18 } } },
{ $group: { _id: "$city", total: { $sum: 1 } } }
]);
typescript
性能优化技巧:
- 合理设计文档结构避免过度嵌套
- 使用投影减少返回数据量
- 创建适当的索引
- 批量操作使用bulkWrite
数据库选型指南
新兴趋势:
- Edge数据库:如SQLite的Wasm版本
- 时序数据库:IoT场景下的TimescaleDB
- 图数据库:社交关系场景的Neo4j
混合使用案例:
- 用户数据用PostgreSQL(关系型)
- 产品目录用MongoDB(文档型)
- 缓存用Redis(键值型)
下节课将深入演示多数据库环境下的跨库事务处理方案 🚀
多租户架构设计原理
租户标识的核心作用
多租户系统的核心在于通过租户标识实现数据隔离,这种设计在SaaS平台和企业级应用中尤为重要:
- 标识类型:
- 域名(如
tenant1.app.com
) - HTTP Header(
X-Tenant-ID
) - JWT Claims(
tenant_id
字段) - 子路径(
app.com/tenant1
)
- 域名(如
- 隔离级别对比:
隔离方式 实现复杂度 安全性 性能影响 适用场景 独立数据库 高 极高 低 金融/医疗等高安全需求 共享数据库独立Schema 中 高 中 中型SaaS应用 共享表租户字段 低 中 高 小型应用快速开发 - 物理隔离优势:
- 完全避免数据泄露风险
- 支持租户自定义数据库结构
- 便于实现租户专属备份恢复
- 资源分配可精确控制(如连接池大小)
技术实现深度解析
1. 请求路由层实现
NestJS中间件示例:
@Injectable()
export class TenantMiddleware implements NestMiddleware {
constructor(private configService: ConfigService) {}
async use(req: Request, res: Response, next: NextFunction) {
const tenantId = req.headers['x-tenant-id'] || this.parseSubdomain(req);
if (!tenantId) throw new UnauthorizedException();
const tenantConfig = await this.configService.getTenantConfig(tenantId);
req['tenant'] = tenantConfig; // 附加租户配置到请求对象
next();
}
private parseSubdomain(req: Request) {
return req.hostname.split('.')[0];
}
}
typescript
2. 动态数据源切换
TypeORM实现方案:
// tenant-aware.service.ts
@Injectable()
export class TenantService {
private dataSources = new Map<string, DataSource>();
async getDataSource(tenantId: string): Promise<DataSource> {
if (!this.dataSources.has(tenantId)) {
const config = this.getTenantConfig(tenantId);
const ds = new DataSource(config);
await ds.initialize();
this.dataSources.set(tenantId, ds);
}
return this.dataSources.get(tenantId);
}
}
typescript
3. 跨租户查询保护
全局查询过滤器:
// 在数据源配置中添加过滤
new DataSource({
...config,
filters: {
tenantFilter: {
condition: (tenantId) => `tenant_id = '${tenantId}'`,
required: true,
}
}
});
// 实体标记
@Entity()
@Filter('tenantFilter', (req) => ({ tenantId: req['tenant'].id }))
export class Product {
@Column()
tenantId: string;
}
typescript
高级架构模式
混合隔离策略
性能优化方案
- 连接池管理:
// 按租户动态调整连接池 const poolSize = tenant.isPremium ? 20 : 5; dataSource.setOptions({ poolSize });
typescript - 缓存策略:
- Redis多租户缓存:
redis://cache/tenant1
- 缓存键设计:
tenant1:product:123
- Redis多租户缓存:
- 异步操作队列:
// 为每个租户创建独立队列 const queue = new Queue(`tenant-${tenantId}-emails`);
typescript
企业级解决方案对比
方案 | 代表产品 | 适用规模 | 年成本估算 |
---|---|---|---|
完全独立实例 | AWS RDS多实例 | 大型企业 | $50k+ |
云原生多租户数据库 | Azure SQL弹性池 | 中型企业 | 15k−50k |
共享实例+逻辑隔离 | PostgreSQL Schema | 创业公司 | <$10k |
常见问题解决方案
Q:如何处理租户间共享数据?
// 在全局模块中配置共享数据源
@Global()
@Module({
providers: [{
provide: 'SHARED_DB',
useFactory: () => new DataSource(sharedConfig),
}],
exports: ['SHARED_DB'],
})
export class SharedDbModule {}
typescript
Q:如何实现租户数据迁移?
# 使用TypeORM迁移脚本+租户过滤
typeorm migration:run -t tenant_123
bash
Q:多租户系统如何监控?
- Prometheus指标添加
tenant_id
标签 - 日志系统自动附加租户上下文
- APM工具按租户分析性能
下节课将演示如何在Kubernetes中部署多租户数据库集群,实现自动扩缩容和故障转移 🔥
TypeORM多数据库配置实战
配置步骤详解
1. 命名连接配置(多数据库核心配置)
完整配置项说明:
TypeOrmModule.forRoot({
name: 'order_db', // 连接名称(必填)
type: 'postgres', // 数据库类型
host: 'db.order.svc.cluster.local', // Kubernetes集群内地址
port: 5432,
username: 'order_user',
password: process.env.DB_PASSWORD, // 从环境变量读取
database: 'order_service',
schema: 'order_v1', // PostgreSQL schema隔离
entities: [Order, OrderItem], // 实体类注册
synchronize: false, // 生产环境必须关闭
logging: ['query', 'error'], // 开发环境日志
extra: {
connectionLimit: 20, // 连接池配置
idleTimeoutMillis: 30000
}
}),
typescript
动态配置方案(适合云环境):
// 从配置中心动态获取配置
TypeOrmModule.forRootAsync({
name: 'inventory_db',
useFactory: (config: ConfigService) => ({
type: 'mysql',
host: config.get('INVENTORY_DB_HOST'),
entities: [Inventory, Warehouse],
retryAttempts: 5 // 连接重试策略
}),
inject: [ConfigService]
})
typescript
2. 实体注册绑定(跨库操作关键)
多数据库实体管理:
// products/product.entity.ts
@Entity({ database: 'product_db' }) // 显式指定数据库
@Index(['sku', 'tenant_id']) // 复合索引
export class Product {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 50 })
sku: string;
@Column({ name: 'tenant_id' }) // 租户隔离字段
tenantId: string;
}
// orders/order.entity.ts
@Entity({ database: 'order_db' })
export class Order {
@PrimaryColumn()
id: string;
@ManyToOne(() => Product)
@JoinColumn({ name: 'product_id' })
product: Product; // 跨库关联(需要特殊处理)
}
typescript
跨库关联解决方案:
// 使用ReferenceId代替直接关联
@Column()
productId: string; // 存储产品ID而非关系
// 手动查询关联数据
async getOrderWithProduct(orderId: string) {
const order = await orderRepo.findOne(orderId);
const product = await productRepo.findOne(order.productId);
return { ...order, product };
}
typescript
避坑指南增强版
错误场景 | 正确方案 | 技术原理 | 严重性 |
---|---|---|---|
未配置name属性 | 每个连接唯一命名 | TypeORM内部使用name作为连接标识符 | ⚠️ 高危 |
实体未绑定数据库 | 实体类添加@Entity({ database: 'xxx' }) | 元数据存储时确定目标库 | ⚠️ 中危 |
注入未指定名称 | @InjectRepository(Order, 'order_db') | 依赖注入时需要上下文信息 | ⚠️ 高危 |
混用不同库事务 | 使用分布式事务(XA)或Saga模式 | 单事务管理器无法跨库 | 💥 致命 |
未关闭同步功能 | synchronize: false | 生产环境自动建表风险 | ⚠️ 高危 |
连接池优化参数:
# 建议生产环境配置
extra:
max: 50 # 最大连接数
min: 5 # 最小保持连接
acquireTimeout: 30000 # 获取连接超时(ms)
idleTimeout: 600000 # 空闲连接超时(10分钟)
yaml
高级配置技巧
1. 读写分离配置
TypeOrmModule.forRoot({
name: 'reporting_db',
type: 'mysql',
replication: {
master: { host: 'master.reporting.db' },
slaves: [
{ host: 'replica1.reporting.db' },
{ host: 'replica2.reporting.db' }
]
},
entities: [Report],
})
typescript
2. 多租户动态数据源
// 动态创建数据源
async createTenantDataSource(tenantId: string) {
const config = await fetchTenantConfig(tenantId);
return new DataSource({
name: `tenant_${tenantId}`, // 动态命名
...config,
entities: [/* 动态加载实体 */]
}).initialize();
}
typescript
3. 健康检查集成
// health.module.ts
@Module({
imports: [
TerminusModule.forRoot({
endpoints: [{
path: '/health',
healthIndicators: [
async () => {
const orderDb = getDataSource('order_db');
return { 'order_db': orderDb.isInitialized ? 'up' : 'down' };
}
]
}]
})
]
})
typescript
性能优化方案
- 连接预热(启动时建立最小连接数):
// app.module.ts async onApplicationBootstrap() { await dataSource.query('SELECT 1'); // 触发连接建立 }
typescript - 批量操作优化:
// 使用Repository.save批量插入 await repo.save(products, { chunk: 100 }); // 每100条分批提交
typescript - 查询缓存:
// 二级缓存配置 TypeOrmModule.forRoot({ cache: { type: 'redis', options: { host: 'redis', port: 6379 }, duration: 30000 // 30秒缓存 } })
typescript
监控与调试
日志分析建议:
# 查看慢查询
grep 'duration: [0-9]{4,}' typeorm.logs
# 连接泄漏检测
监控连接数变化:max_used_connections / max_connections > 0.8 告警
bash
Prometheus监控指标:
# 建议采集指标
- typeorm_connections_active
- typeorm_query_duration_seconds
- typeorm_errors_total
yaml
下节课将结合Docker演示多数据库环境的CI/CD流水线配置,包括迁移脚本自动执行和回滚方案 🛠️
依赖注入使用多数据源
依赖注入深度解析
1. 多仓库注入原理
TypeORM通过@InjectRepository()
装饰器实现多数据源注入,其核心机制是:
- 令牌生成:
getRepositoryToken(Entity, connectionName)
- Provider注册:
TypeOrmModule.forFeature()
自动注册 - 依赖解析:NestJS容器根据令牌匹配实例
// 底层等价实现
providers: [
{
provide: getRepositoryToken(User, 'db1'),
useFactory: (dataSource: DataSource) => dataSource.getRepository(User),
inject: ['db1'], // 指定注入的DataSource
}
]
typescript
2. 动态数据源高级用法
运行时动态注入:
@Injectable()
export class DynamicRepositoryService {
constructor(
@InjectConnection() // 获取默认连接
private defaultConnection: DataSource
) {}
async getTenantRepository<T>(tenantId: string, entity: EntityTarget<T>) {
const ds = await this.getOrCreateDataSource(tenantId);
return ds.getRepository(entity);
}
}
typescript
操作示例增强版
1. 跨库事务处理
async createCrossDatabaseOrder() {
const queryRunner = this.userRepo.manager.connection.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
// 操作db1
const user = await queryRunner.manager
.getRepository(User, 'db1')
.findOne({ where: { id: 1 } });
// 操作db2
await queryRunner.manager
.getRepository(Product, 'db2')
.insert({ name: 'Premium Package', userId: user.id });
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
typescript
2. 多租户批量操作
async migrateTenantData(sourceTenant: string, targetTenant: string) {
const [sourceRepo, targetRepo] = await Promise.all([
this.getTenantRepository(sourceTenant, Product),
this.getTenantRepository(targetTenant, Product)
]);
const products = await sourceRepo.find();
await targetRepo.insert(products); // 批量插入
// 使用流式处理大数据量
const productStream = sourceRepo.createQueryBuilder().stream();
productStream.on('data', async (product) => {
await targetRepo.save({ ...product, id: undefined });
});
}
typescript
动态数据源切换方案对比
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
@InjectRepository | 类型安全,声明式 | 需预定义连接 | 固定多数据源 |
DataSource#getRepository | 运行时动态 | 需手动管理生命周期 | SaaS多租户 |
自定义Repository | 可封装业务逻辑 | 实现复杂度高 | 需要特殊查询的场景 |
性能优化技巧
- 仓库实例缓存:
const repoCache = new Map<string, Repository<any>>();
async getCachedRepository(connName: string, entity: any) {
const key = `${connName}:${entity.name}`;
if (!repoCache.has(key)) {
const ds = await getDataSource(connName);
repoCache.set(key, ds.getRepository(entity));
}
return repoCache.get(key)!;
}
typescript
- 连接预热策略:
// 应用启动时初始化常用仓库
async preloadRepositories() {
await Promise.all([
this.userRepo.find(),
this.productRepo.find()
]);
}
typescript
错误处理最佳实践
多数据源操作异常处理模板:
async safeOperation() {
try {
const [user, product] = await Promise.all([
this.userRepo.findOne().catch(() => null),
this.productRepo.findOne().catch(() => null)
]);
if (!user || !product) {
throw new BusinessException('DATA_NOT_FOUND');
}
// 业务逻辑...
} catch (err) {
if (err instanceof QueryFailedError) {
// 数据库级错误处理
throw new DbException(err.message);
}
throw err;
}
}
typescript
监控与调试
日志标记方案:
// 为所有查询添加数据源标记
DataSource.prototype.createQueryBuilder = function () {
const qb = originalCreateQueryBuilder.apply(this, arguments);
qb.expressionMap.mainAlias!.metadata.connectionName = this.name;
return qb;
};
// 日志输出示例
// [QUERY] db1 - SELECT * FROM users
typescript
APM集成:
// 使用OpenTelemetry追踪
const tracer = trace.getTracer('typeorm');
async function tracedQuery(query: string) {
return tracer.startActiveSpan('db.query', span => {
span.setAttribute('db.connection', this.name);
return originalQuery.apply(this, [query]);
});
}
typescript
扩展应用场景
- 多云数据库联邦查询:
async federatedQuery() {
const [localData, cloudData] = await Promise.all([
this.localRepo.find(),
this.cloudRepo.createQueryBuilder()
.where('region = :region', { region: 'APAC' })
.getMany()
]);
return [...localData, ...cloudData];
}
typescript
- 数据归档系统:
async archiveOldData() {
const oldData = await this.currentRepo.find({
where: { createdAt: LessThan(oneYearAgo) }
});
await this.archiveRepo.insert(oldData);
await this.currentRepo.delete({
createdAt: LessThan(oneYearAgo)
});
}
typescript
下节课将结合AWS RDS Proxy演示生产环境下的连接池管理与故障转移策略 ☁️
总结与演进路线
技术演进路线
从基础到高级的多数据库管理技术发展路径:
- 单库操作
- 基础CRUD操作
- 单一数据库连接配置
- 适合小型应用或初期开发
- 多库配置
- 支持多种数据库类型(PostgreSQL、MySQL等)
- 通过命名连接实现多数据源管理
- 适用于微服务架构或模块化设计
- 租户隔离
- 基于租户标识动态路由数据源
- 支持物理隔离(独立数据库)或逻辑隔离(Schema/字段隔离)
- 适用于SaaS平台或企业级多租户系统
- 动态连接
- 运行时按需创建和销毁数据库连接
- 支持云原生环境下的弹性扩展
- 适合高并发或动态租户管理的场景
最佳实践建议
1. 生产环境连接池管理
- 配置优化:
TypeOrmModule.forRoot({ extra: { max: 50, // 最大连接数 min: 5, // 最小空闲连接 idleTimeout: 30000, // 空闲超时时间(毫秒) } })
typescript - 监控指标:
- 活跃连接数
- 连接等待时间
- 连接泄漏检测
2. 租户数据库白名单机制
- 实现方案:
- 在中间件中验证租户标识是否合法
- 动态限制租户的数据库访问权限
@Middleware() async use(req: Request, res: Response, next: NextFunction) { const tenantId = req.headers['x-tenant-id']; if (!allowedTenants.includes(tenantId)) { throw new ForbiddenException('租户无权限访问'); } next(); }
typescript
3. 事务回滚逻辑
- 关键操作保护:
async transferFunds() { const queryRunner = dataSource.createQueryRunner(); try { await queryRunner.startTransaction(); await queryRunner.manager.update(Account, { id: 1 }, { balance: -100 }); await queryRunner.manager.update(Account, { id: 2 }, { balance: +100 }); await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } }
typescript
下节课预告
实战内容:
- 多租户系统的完整配置流程
- 动态数据源与连接池的集成
- 基于Kubernetes的数据库弹性扩缩容
案例演示:
- 从零搭建一个支持多租户的SaaS后台
- 模拟高并发场景下的性能优化
准备好你的开发环境,我们即将进入实战环节! 🚀
↑