- 后端
【免费下载链接】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.
初始化应用或在测试中搭建数据库时,手工准备样例数据往往繁琐且重复。MikroORM 的 seeding 机制提供了解决方案:通过 Seeder 类组织种子脚本、配合实体工厂(Entity Factory)批量生成数据,即可用几条命令快速填充数据库。本篇指南基于 docs/versioned_docs/version-7.2/seeding.md 与@mikro-orm/seeder包源码,完整讲解配置项、CLI 命令、Seeder 编写、工厂定义与关系生成,并给出测试与生产环境中的落地方式,读完即可在项目中直接上手。
安装与启用 SeedManager
Seeder 功能由独立的@mikro-orm/seeder包提供。首先安装该依赖:
npm install @mikro-orm/seeder然后在 ORM 配置中注册SeedManager扩展。以mikro-orm.config.ts为例:
import { SeedManager } from '@mikro-orm/seeder'; export default defineConfig({ // ... extensions: [SeedManager], });从源码实现看,SeedManager.register()会通过orm.config.registerExtension('@mikro-orm/seeder', ...)把种子管理器挂到 ORM 实例上(见 SeedManager.ts),之后即可通过orm.seeder访问。核心包在 MikroORM.ts 中暴露了get seeder()访问器,用于运行种子脚本;在MikroORM.init()时,MikroORM.ts 也会尝试加载该扩展。注意@mikro-orm/seeder与@faker-js/faker相互独立:自 v6 起,Faker 不再从 seeder 包中重新导出,需要单独安装。
配置项与默认值
Seeder 的默认设置可通过 MikroORM 配置轻松覆盖。seeder.path与seeder.pathTs的作用方式与实体发现中的entities/entitiesTs一致:前者指向编译后的 JS 文件目录,后者指向 TS 源码目录。以下是完整配置项及其默认值:
MikroORM.init({ seeder: { path: './seeders', // seeder 文件目录(编译后的 JS) pathTs: undefined, // TS seeder 文件目录(使用时应将编译产物路径放到 path) defaultSeeder: 'DatabaseSeeder',// 默认 seeder 类名 glob: '!(*.d).{js,ts}', // seeder 文件匹配规则(所有 .js/.ts,排除 .d.ts) emit: 'ts', // seeder 生成模式('js' | 'ts') fileName: (className: string) => className, // seeder 文件命名约定 }, });这些选项同样定义在核心包的 Configuration.ts 的SeederOptions接口中,并额外支持seedersList字段——直接以类引用或{ name, class }对象形式注册 seeder,替代基于文件系统的发现机制(此时SeedManager.seedString()会优先查表而不是扫描目录,见 SeedManager.ts)。
配置还支持通过环境变量覆盖,变量前缀为MIKRO_ORM_SEEDER_(参见 env-vars.ts):
MIKRO_ORM_SEEDER_PATHMIKRO_ORM_SEEDER_PATH_TSMIKRO_ORM_SEEDER_EMITMIKRO_ORM_SEEDER_GLOBMIKRO_ORM_SEEDER_DEFAULT_SEEDER
一个值得注意的自动探测行为:SeedManager.init()内部会在未显式配置路径时检查项目是否存在src、dist、build目录,若./seeders默认目录不存在,会自动把path指向./dist/seeders或./build/seeders、pathTs指向./src/seeders,帮助新项目免配置启动(见 SeedManager.ts)。
编写 Seeder 类
Seeder 类继承自@mikro-orm/seeder的Seeder基类,只需实现一个run(em)方法。执行npx mikro-orm seeder:run时,该方法即被调用,在其中定义要向数据库写入什么数据、如何写入。可以用 EntityManager 直接创建实体,也可以使用实体工厂(见下文)。
用 CLI 生成 Seeder 骨架
CLI 提供seeder:create命令,参数可以是名字、类名或连字符形式的名字:
npx mikro-orm seeder:create DatabaseSeeder # 生成类 DatabaseSeeder npx mikro-orm seeder:create test # 生成类 TestSeeder npx mikro-orm seeder:create project-names # 生成类 ProjectNamesSeeder命令默认在./seeders/目录生成文件,目录可通过seeder.path或环境变量MIKRO_ORM_SEEDER_PATH修改。类名格式化逻辑在 CreateSeederCommand.ts:剥离名称中已有的Seeder后缀、按-分段并首字母大写,最后统一拼接Seeder后缀。生成的文件内容由emit决定——TS 模式生成export class XxxSeeder extends Seeder,JS 模式生成 CommonJS 风格模块(见 SeedManager.ts)。
一个最基本的 Seeder
import { EntityManager } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; import { Author } from './author' export class DatabaseSeeder extends Seeder { async run(em: EntityManager): Promise<void> { // 会自动持久化 const author = em.create(Author, { name: 'John Snow', email: 'snow@wall.st' }); // 如果改用 `const author = new Author()` 创建, // 则需要显式调用 `em.persist(author)`。 } }需要特别说明的是:seeder 场景下的 EntityManager 强制启用了persistOnCreate(即使 ORM 全局配置中显式关闭也是如此)——SeedManager构造时会对 fork 出的 EM 执行this.#config.set('persistOnCreate', true)(见 SeedManager.ts)。因此em.create()会自动附带em.persist();而通过实体构造函数new Author()创建的对象不会被自动跟踪,必须手动em.persist()。
此外,无论通过命令行还是编程方式运行 seeder,run方法执行完毕后都会自动执行一次flush和clear:SeedManager.seed()在每次运行 seeder 后调用this.#em.flush()与this.#em.clear()(见 SeedManager.ts)。
在 Seeder 中使用实体工厂
与其为每个实体手写全部属性,不如使用实体工厂批量生成大量记录。在run中实例化工厂并调用make即可:
import { EntityManager } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; import { AuthorFactory } from '../factories/author.factory' export class DatabaseSeeder extends Seeder { async run(em: EntityManager): Promise<void> { new AuthorFactory(em).make(10); // 生成 10 个 author 实体 } }注意:make只负责生成并持久化(persist)实体,不会立即 flush;最终 flush 由 SeedManager 在 seeder 结束后统一完成。
组合多个 Seeder:call 方法与共享上下文
当单个 seeder 文件过大时,可以用call方法把数据库种子脚本拆分成多个文件。call接收一个em和一个 seeder 类数组:
import { EntityManager } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; import { AuthorSeeder, BookSeeder } from '../seeders' export class DatabaseSeeder extends Seeder { run(em: EntityManager): Promise<void> { return this.call(em, [ AuthorSeeder, BookSeeder, ]); } }从 Seeder.ts 的源码可以看到call的实现细节:它会为每个子 seederem.fork()出一个独立上下文,实例化后执行其run方法,并在每个子 seeder 结束后单独flush——这意味着子 seeder 之间天然隔离,但也意味着跨 seeder 的实体引用不能依赖同一 EM 的隐式追踪,而要通过共享上下文传递。
call会自动创建共享上下文对象,作为run方法的第二个参数传给每个子 seeder。这非常适合生成相互引用的实体:AuthorSeeder先把作者写入上下文,BookSeeder再从中取出并关联:
export class AuthorSeeder extends Seeder { async run(em: EntityManager, context: Dictionary): Promise<void> { // 把实体保存到上下文 context.author = em.create(Author, { name: '...', email: '...', }); } }export class BookSeeder extends Seeder { async run(em: EntityManager, context: Dictionary): Promise<void> { em.create(Book, { title: '...', author: context.author, // 使用上下文中的实体 }); } }实体工厂(Entity Factory)
测试场景下,与其在测试开始前手工编写每条记录的每个属性,不如用Factory为实体定义一组默认属性。下面是一个针对 Author 实体的工厂示例:
import { Factory } from '@mikro-orm/seeder'; import { faker } from '@faker-js/faker'; import { Author } from './entities/author.entity'; export class AuthorFactory extends Factory<Author> { model = Author; definition(): Partial<Author> { return { name: faker.person.findName(), email: faker.internet.email(), age: faker.random.number(18, 99), }; } }工厂需要满足两点:model属性指明工厂为哪个实体生成实例;definition方法返回创建实体时应用的默认属性集。示例中借助 Faker 库方便地生成各类随机测试数据。
从基类实现看,Factory<TEntity, TInput>的makeEntity会调用definition得到默认数据,再通过this.em.create(model, data, { persist: false })创建实体并应用each回调(见 Factory.ts)。
用工厂创建实体
工厂定义好后即可用于生成实体:导入工厂、实例化并调用makeOne:
const author = new AuthorFactory(orm.em).makeOne();生成多个实体
调用make方法,参数为生成数量:
// 生成 5 个 authors const authors = new AuthorFactory(orm.em).make(5);覆盖默认属性
可以向make/makeOne传入对象覆盖部分默认值,只有指定属性被替换,其余保持工厂默认值:
// 生成单个 author const author = new AuthorFactory(orm.em).makeOne({ name: 'John Snow', }); // 生成 5 个 authors const authors = new AuthorFactory(orm.em).make(5, { name: 'John Snow', });持久化实体
create/createOne在实例化实体的同时,通过 EntityManager 的persist+flush将其写入数据库:
// 生成并持久化单个 author const author = await new AuthorFactory(orm.em).createOne(); // 生成并持久化 5 个 authors const authors = await new AuthorFactory(orm.em).create(5);同样支持传入覆盖参数:
// 生成并持久化单个 author const author = await new AuthorFactory(orm.em).createOne({ name: 'John Snow', }); // 生成并持久化 5 个 authors const authors = await new AuthorFactory(orm.em).create(5, { name: 'John Snow', });各方法的差异在 Factory.ts 中一目了然:makeOne/make只调用em.persist()(不 flush),createOne/create则在此基础上额外执行await this.em.flush()(见 Factory.ts)。在 seeder 场景下,由于 SeedManager 结束时会统一 flush,make系列通常就已足够;在测试中若需立即落库,则应使用create系列。
工厂关系生成
单实体的海量数据生成固然方便,但多数情况下你需要同时为多个实体造数并建立它们之间的关系。
通过.each()定义关系
each方法可以链式调用,接收一个对工厂输出实体进行转换的函数(在实体返回前应用):
// ManyToOne / OneToOne 关系 const books: Book[] = new BookFactory(orm.em).each(book => { book.author = new AuthorFactory(orm.em).makeOne(); }).make(5);// OneToMany / ManyToMany 关系 const books: Book[] = new BookFactory(orm.em).each(book => { book.owners.set(new OwnerFactory(orm.em).make(5)); }).make(5);从 Factory.ts 可见,each只是保存一个回调(接收实体与序号 index),由makeEntity在实体创建后调用,因此也适用于create系列(在持久化前应用)。
通过.definition()定义关系
另一种方式是在definition方法内部构建嵌套实体。该方法还可以接收不属于实体 schema 的额外参数:
export class AuthorFactory extends Factory< AuthorEntity, EntityData<AuthorEntity> & { booksCount?: number } > { model = AuthorEntity; async definition( params?: EntityData<AuthorEntity> & { booksCount?: number } ): EntityData<AuthorEntity> { const name = params.name ?? faker.person.findName(); const books = params.books ?? ( [...Array(params?.booksCount ?? 0)].map((v, i) => new BookFactory(this.em).makeEntity({ title: `${name} Trilogy - Part ${i + 1}` }) ) ); return { ...params, name, books }; } } // 最终调用 new AuthorFactory(em).createOne({ booksCount: 4 })这里通过第二个泛型参数扩展了TInput类型,使工厂支持自定义输入参数(如booksCount),并在definition中利用this.em(基类构造时保存的 EntityManager)嵌套调用其他工厂生成关联实体。注意makeEntity在定义关系时是理想选择——它创建实体但不持久化,避免生成中间实体产生多余写入。
在命令行中使用
执行seeder:run即可为数据库填充种子数据。默认运行DatabaseSeeder类(其内部可继续调用其他 seeder),默认类可通过seeder.defaultSeeder或环境变量MIKRO_ORM_SEEDER_DEFAULT_SEEDER修改,也可用--class显式指定:
npx mikro-orm seeder:run npx mikro-orm seeder:run --class=BookSeederCLI 命令的实现见 DatabaseSeedCommand.ts:seeder:run支持-c, --class选项,最终调用orm.seeder.seedString(className),未指定时回退到配置的defaultSeeder(见 DatabaseSeedCommand.ts)。
migrate:fresh与schema:fresh命令配合--seed选项,可以做到"全量重建":先删除所有表,再重跑全部迁移(或基于当前实体重新生成 schema),最后执行DatabaseSeeder:
npx mikro-orm migration:fresh --seed # 删库、运行全部迁移并执行 DatabaseSeeder npx mikro-orm schema:fresh --seed # 重建数据库并执行 DatabaseSeeder若不想运行DatabaseSeeder,可修改默认 seeder 配置,或直接显式指定类名:
npx mikro-orm migration:fresh --seed TestSeeder # 删库、运行全部迁移并执行 TestSeeder npx mikro-orm schema:fresh --seed ProjectsSeeder # 重建数据库并执行 ProjectsSeeder在测试中使用
有了 seeder 和工厂,测试前初始化数据变得非常简洁。典型模式如下:
let orm: MikroORM; beforeAll(async () => { // 初始化 ORM orm = await MikroORM.init({ ... }); // 刷新数据库,从干净状态开始 await orm.schema.refresh(); // 然后运行 seeder await orm.seeder.seed(DatabaseSeeder); }); test(() => { // 执行测试 }); afterAll(async () => { // 关闭连接 await orm.close(); });其中orm.seeder.seed(DatabaseSeeder)直接以类引用运行 seeder(对应SeedManager.seed(),见 SeedManager.ts),与 CLI 按类名字符串查找的seedString()不同,适合在测试等"已加载类"的场景使用。仓库测试目录中也有现成范例,例如 tests/database/seeder/database.seeder.ts 演示了call(em, [UserSeeder, ProjectSeeder])的组合写法。
生产环境运行编译后的 Seeder
在生产环境,你可能希望使用编译后的 seeder 文件。只需相应配置 ORM 中的 seeder 路径:
import { MikroORM, Utils } from '@mikro-orm/core'; await MikroORM.init({ seeder: { path: 'dist/seeders', pathTs: 'src/seeders', }, // 或者用自动检测: // seeder: { // path: Utils.detectTypeScriptSupport() ? 'src/seeders' : 'dist/seeders', // }, // ... });这样可以在 CLI 中(通常已启用 TS 支持)生成 TS 格式的 seeder 文件,同时在生产环境使用编译后的 JS 文件。运行时是否启用 TS 模式由preferTs配置与Utils.detectTypeScriptSupport()共同决定——SeedManager 初始化时会据此在pathTs与path之间选择实际扫描目录(见 SeedManager.ts)。
小结
MikroORM 的 seeding 体系由三层构成:Seeder基类(Seeder.ts)负责组织种子脚本与组合执行,Factory基类(Factory.ts)负责按默认定义批量生成实体,SeedManager(SeedManager.ts)负责目录发现、类加载与执行编排。三者配合seeder:create、seeder:run、schema:fresh --seed、migration:fresh --seed等 CLI 命令,即可在开发、测试与生产环境中统一、可重复地管理数据库初始数据——这也是初始化演示数据、搭建测试基座、快速重建环境时最省力的实践路径。
- 后端
【免费下载链接】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.
相关推荐
MikroORM Seeding 完整指南:用 Seeder 与 Entity Factory 高效填充数据库
MikroORM Seeding 完整指南:用 Seeder 与 Entity Factory 高效填充数据库 在初始化应用、跑测试或搭建演示环境时,手动逐条插
后端Phinx数据库种子(Seeding)功能详解
Phinx数据库种子 Seeding 功能详解 什么是数据库种子 Seeding 数据库种子 Seeding 是Phinx从0.5.0版本开始引入的一项重要功能
数据库后端开发工具MikroORM Seeding 实战指南:用 Seeders 与 Entity Factories 高效填充数据库
MikroORM Seeding 实战指南:用 Seeders 与 Entity Factories 高效填充数据库 初始化应用、编写集成测试时,手动为数据库准
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考