Nest.js + TypeORM:如何在不同Schema中执行相同迁移?
问题:多租户Schema迁移失败
我正在开发一个多租户应用,每个租户拥有独立Schema,示例Schema如下:
- public(默认)
- tenant_001
- tenant_002
目前能在public Schema中正常执行迁移,但其他租户Schema无法完成完整迁移——仅生成了migrations表,没有创建其他业务表。
代码片段
迁移执行逻辑
... ... const conn = await dataSource('default').initialize(); // await conn.synchronize(); await conn.runMigrations(); const schemas = await conn.query( 'select schema_name as name from information_schema.schemata;', ); for (let i = 0; i < schemas.length; i += 1) { const { name: schema } = schemas[i]; if (schema.startsWith('tenant_')) { const connection = await getTenantConnection(schema); // await connection.synchronize(); await connection.runMigrations(); await connection.destroy(); } } ... ...
获取租户连接
export async function getTenantConnection( tenantId: string, ): Promise<DataSource> { const connectionManager = dataSource(tenantId); if (connectionManager.isInitialized) { return await connectionManager; } return await connectionManager.initialize(); }
DataSource生成函数
export const dataSource = (schemaName: string) => { if (schemaName === 'default' || schemaName === '') { return new DataSource(connectionConfig); } else { const newConnectionConfig = { ...connectionConfig, schema: schemaName, autoLoadEntities: true, name: schemaName, }; console.log({ newConnectionConfig }); return new DataSource(newConnectionConfig); } };
基础连接配置
const connectionConfig: DataSourceOptions = { type: process.env.DB_TYPE as any, host: process.env.DB_HOST, port: +process.env.DB_PORT, username: process.env.DB_USR, password: process.env.DB_PWD, database: process.env.DB_NAME, logging: true, synchronize: true, entities: ['dist/**/*.entity{.ts,.js}'], migrations: ['dist/**/*.migration{.ts,.js}'], name: 'default', };
实际结果
租户Schema中仅存在migrations表,没有生成业务实体对应的表。
预期结果
所有租户Schema应与默认public Schema执行完全相同的迁移操作,生成所有业务表。
环境信息
- macOS
- 本地PostgreSQL 15
- NestJS
- TypeORM
问题分析与解决方案
核心问题
- 迁移脚本未绑定动态Schema,执行时默认操作public Schema而非当前租户Schema
- 实体未指定对应Schema,连接初始化后实体无法映射到租户Schema
- 基础配置中
synchronize: true与迁移脚本存在冲突,可能干扰表结构生成
具体修复步骤
1. 给实体绑定动态Schema
修改实体类,通过装饰器动态获取当前连接的Schema:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; import { getConnection } from 'typeorm'; @Entity({ schema: () => getConnection().options.schema || 'public' }) export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; }
2. 修改迁移脚本适配多租户
手动编写的迁移脚本需在up/down方法中指定当前Schema:
import { MigrationInterface, QueryRunner } from 'typeorm'; export class CreateUserTable1620000000000 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { const schema = queryRunner.connection.options.schema || 'public'; await queryRunner.query(`CREATE TABLE "${schema}"."user" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`); } public async down(queryRunner: QueryRunner): Promise<void> { const schema = queryRunner.connection.options.schema || 'public'; await queryRunner.query(`DROP TABLE "${schema}"."user"`); } }
3. 调整DataSource配置
确保租户连接的迁移表名绑定当前Schema,并关闭自动同步避免冲突:
// 基础连接配置修改 const connectionConfig: DataSourceOptions = { // ...其他配置 synchronize: false, // 关闭自动同步,统一用迁移脚本管理 // ... }; // DataSource生成函数修改 export const dataSource = (schemaName: string) => { if (schemaName === 'default' || schemaName === '') { return new DataSource(connectionConfig); } else { const newConnectionConfig = { ...connectionConfig, schema: schemaName, autoLoadEntities: true, name: schemaName, migrationsTableName: 'migrations', // 明确迁移表在当前Schema下 }; console.log({ newConnectionConfig }); return new DataSource(newConnectionConfig); } };
4. 强化租户迁移的Schema上下文
执行迁移前先切换到目标Schema,确保操作上下文正确:
// 迁移循环逻辑修改 if (schema.startsWith('tenant_')) { const connection = await getTenantConnection(schema); await connection.query(`SET search_path TO "${schema}"`); await connection.runMigrations(); await connection.destroy(); }
内容的提问来源于stack exchange,提问作者Aravin
相关产品推荐
相关产品推荐

