☰
MikroORM 多 Schema 使用指南:实体定义、运行时切换与通配符 Schema 原理
2026/9/25 5:47:06 网站建设 项目流程
  • 后端

【免费下载链接】mikro-orm

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.

项目地址:https://gitcode.com/gh_mirrors/mi/mikro-orm
点击查看免费下载

导读

本文围绕 MikroORM 的多 Schema 支持展开,讲解在 MySQL、PostgreSQL 等数据库中如何通过schema选项、tableName前缀、EntityManager默认 Schema 与QueryBuilder.withSchema()在多个 Schema 之间切换查询与写入,并深入剖析 v5 引入的通配符 Schema(schema: '*')在多租户场景下的工作原理及其与SchemaGenerator、Migrations 的协作边界。读完本文,你将掌握从"静态多 Schema 建模"到"运行时动态切换 Schema"的完整实战方案。

一、什么是多 Schema:MySQL 与 PostgreSQL 的差异

在 MySQL 和 PostgreSQL 中,实体可以定义在多个 Schema 中。MySQL 术语里通常称之为"数据库"(database),但从实现角度看它就是 Schema。MikroORM 在 SQL 方言层统一抽象了"Schema"这一概念,因此两套数据库在使用方式上完全一致。

使用多 Schema 的前提条件非常关键:

连接(connection)必须能够访问所有 Schema;单个 MikroORM 实例不支持多个连接。

这意味着你不需要(也不能)为每个 Schema 单独初始化一个 ORM 实例,而是由同一个连接、同一个实体元数据在运行时决定 SQL 落到哪个 Schema。这一约束在源码层面贯穿始终:ConnectionOptions中只有一个可选的默认schema字段(见 Configuration.ts),后续所有 Schema 切换都发生在查询执行层。

二、在实体定义阶段指定 Schema

定义实体时,有两种等价方式告诉 MikroORM 该实体属于哪个 Schema:

@Entity({ schema: 'first_schema' }) export class Foo { ... } // 或者将 schema 名拼进自定义表名 @Entity({ tableName: 'second_schema.bar' }) export class Bar { ... }

两种方式最终殊途同归:生成的 SQL 会直接使用带 Schema 前缀的完整表名(如second_schema.bar)。只要连接拥有对应 Schema 的访问权限,查询、插入、更新、删除以及关系加载都会正常工作,业务代码无需感知 Schema 的存在。

三、查询时按操作指定 Schema

除了在实体定义时写死 Schema,你还可以在每次查询时动态指定 Schema,三个入口均支持:

  • EntityManager:em.findOne(User, {...}, { schema: 'client-123' })
  • EntityRepository:同样在 FindOptions 中传入schema
  • QueryBuilder:qb.withSchema('client-123')

典型的场景是"同一实体、多个租户/客户分库":实体定义保持一份,运行时按登录用户的上下文传入不同的 Schema 名。FindOptions.schema是运行时优先级最高的 Schema 来源之一(另一来源是EntityManager.schema与 ORM 配置中的schema),后文通配符 Schema 一节会详细说明这三者的替换顺序。

四、写入指定 Schema:QueryBuilder.withSchema()

与查询不同,通过EntityManager的常规persist流程创建实体时,实体落库的 Schema 由实体元数据决定。要在写入时动态指定 Schema,需要显式使用QueryBuilder:

const qb = em.createQueryBuilder(User); await qb.insert({ email: 'foo@bar.com' }).withSchema('client-123');

withSchema()的底层实现非常直接:它把 Schema 存入查询构建器的内部状态,后续生成 SQL 时据此限定表名。对应实现见 QueryBuilder.ts:

withSchema(schema?: string): this { this.ensureNotFinalized(); this.#state.schema = schema; return this; }

注意ensureNotFinalized()的存在:一旦查询被 finalize(例如已执行或已拼接),就不能再修改 Schema,这与offset、setLockMode等链式方法的保护机制一致。

五、EntityManager 默认 Schema:fork 与上下文感知

如果每个操作都要手动传schema过于繁琐,可以对EntityManager执行.fork()并指定默认 Schema,此后该 fork 上的所有操作都会自动落到该 Schema:

const fork = em.fork({ schema: 'client-123' }); await fork.findOne(User, { ... }); // 等价于 const user = await em.findOne(User, { ... }, { schema: 'client-123' });

创建实体时同样生效——fork 会将默认 Schema 应用到新实体的写入:

const fork = em.fork({ schema: 'client-123' }); const user = new User(); user.email = 'foo@bar.com'; await fork.persistAndFlush(user); // 等价于 const qb = em.createQueryBuilder(User); await qb.insert({ email: 'foo@bar.com' }).withSchema('client-123');

默认 Schema 也可以在 fork 之后随时设置或清除:

em.schema = 'client-123'; const fork = em.fork({ schema: 'client-1234' }); fork.schema = null; // 清除默认 Schema,回退到实体/配置定义

这里有一个值得注意的实现细节:EntityManager.schema的 getter/setter 是**上下文感知(context-aware)**的。源码见 EntityManager.ts:

get schema(): string | undefined { return this.getContext(false).#schema; } set schema(schema: string | null | undefined) { this.getContext(false).#schema = schema ?? undefined; }

getContext(false)意味着:当代码运行在 RequestContext 处理器 内部时,全局的em.schema读写会透明地落到当前请求上下文的 fork 上,从而实现"每个请求一个 Schema、全局代码零侵入"的多租户隔离模式。

六、通配符 Schema:一个实体对应多套 Schema

自 v5 起,MikroORM 支持定义"可存在于多个 Schema"的实体——只要把schema写成通配符'*':

@Entity({ schema: '*' }) export class Book { @PrimaryKey() id!: number; @Property({ nullable: true }) name?: string; @ManyToOne(() => Author, { nullable: true, onDelete: 'cascade' }) author?: Author; @ManyToOne(() => Book, { nullable: true }) basedOn?: Book; }

通配符 Schema 实体是典型的多租户建模方案:每个租户拥有结构完全相同的独立 Schema(如client-1、client-2、client-3),而实体定义只有一份。运行时,通配符会被替换为以下三处之一(按优先级):

  1. FindOptions.schema(单次查询指定);
  2. EntityManager.schema(fork 或em.schema设置);
  3. ORM 配置ConnectionOptions.schema(默认兜底)。

替换逻辑发生在 SQL 生成阶段,因此查询、加载关系、级联删除(如示例中的onDelete: 'cascade')都会自动落到目标 Schema。

6.1 SchemaGenerator 与 CLI --schema

通配符 Schema 实体默认会被SchemaGenerator忽略,因为生成器不知道要为哪个 Schema 建表。需要显式指定目标 Schema:

  • 编程方式:createSchema/updateSchema/dropSchema方法传入schema选项;
  • CLI 方式:npx mikro-orm schema:create --schema client-123等命令的--schema参数。

这一行为在源码中有明确印证:SqlSchemaGenerator会收集所有schema === '*'的元数据并将其表名放入排除列表,只有传入includeWildcardSchema时才跳过该剪枝逻辑。见 SqlSchemaGenerator.ts:

const wildcardSchemaTables = options.includeWildcardSchema ? [] : [...this.metadata.getAll().values()].filter(meta => meta.schema === '*').map(meta => meta.tableName); fromSchema.prune(options.schema, wildcardSchemaTables); toSchema.prune(options.schema, wildcardSchemaTables);

也就是说,不带includeWildcardSchema时,通配符实体被从 diff 的 from/to 两端剪除,避免对未指定 Schema 的实体生成误操作。

6.2 关于 Migrations 的注意事项

当前版本中,通配符 Schema 实体无法直接通过 Migrations 管理:Migrations 会始终忽略通配符 Schema 实体,必须显式使用SchemaGenerator。

考虑到这类实体的动态特性(新租户随时可能被创建),合理的实践是:

  • 在 API 端点中动态同步 Schema(例如注册新租户时调用SchemaGenerator为该租户建表);
  • 仍然保留 ORM Migrations 管理普通实体,但将动态 Schema 的建表 SQL手动写入迁移文件;
  • 对这类动态查询建议开启safe模式(safe: true),避免破坏性操作在误判场景下造成数据损失。

七、整体工作流:从静态定义到动态切换

综合以上内容,一套完整的多 Schema 工作流可以概括为:

  1. 静态多 Schema:实体用schema或tableName前缀固定归属;连接需有全部 Schema 权限;
  2. 运行时查询切换:FindOptions.schema(单次)或em.fork({ schema })/em.schema(会话级);
  3. 运行时写入切换:QueryBuilder.withSchema()显式插入目标 Schema;
  4. 多租户动态 Schema:实体声明schema: '*',结合RequestContext按请求解析实际 Schema,建表通过SchemaGenerator+--schema参数按需同步,迁移文件手动补充动态 SQL。

其中第 4 步与请求上下文配合时,EntityManager.schema的上下文感知 getter/setter 是关键枢纽:全局代码无需改动,每个请求通过 RequestContext 自动获得各自的 Schema 隔离。

八、源码路径速查

能力点源码位置
QueryBuilder.withSchema()状态写入QueryBuilder.ts
EntityManager.schema上下文感知读写EntityManager.ts
连接默认 Schema 配置项Configuration.ts
SchemaGenerator 通配符实体剪枝SqlSchemaGenerator.ts
关联文档multiple-schemas.md、identity-map.md、migrations.md

如需深入了解 Schema 与数据库连接的配置细节,可继续阅读 configuration.md 与 schema-generator.md 两篇指南。

  • 后端

【免费下载链接】mikro-orm

TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.

项目地址:https://gitcode.com/gh_mirrors/mi/mikro-orm
点击查看免费下载
上一篇:智慧教育平台电子课本解析工具:让教材获取变得前所未有的简单
下一篇:如何永久保存微信聊天记录?5步掌握你的数字记忆宝库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询