播种
初始化应用或测试时,为数据库创建示例数据可能非常耗时。解决方案是使用种子。为你的实体创建工厂并在种子脚本中使用它们或组合多个种子脚本。
¥When initializing your application or testing it can be exhausting to create sample data for your database. The solution is to use seeding. Create factories for your entities and use them in the seed script or combine multiple seed scripts.
要使用种子,你需要先安装 @mikro-orm/seeder
,并在 ORM 配置中注册 SeedManager
扩展。
¥To use seeder, you need to first install @mikro-orm/seeder
, and register the SeedManager
extension in your ORM config.
import { SeedManager } from '@mikro-orm/seeder';
export default defineConfig({
// ...
extensions: [SeedManager],
})
配置
¥Configuration
seeder.path
和seeder.pathTs
在实体发现中的工作方式与entities
和entitiesTs
相同。¥
seeder.path
andseeder.pathTs
works the same way asentities
andentitiesTs
in entity discovery.
种子有一些默认设置,可以通过 MikroORM 配置轻松更改。在下面你可以找到配置选项及其默认值。
¥The seeder has a few default settings that can be changed easily through the MikroORM config. Underneath you find the configuration options with their defaults.
MikroORM.init({
seeder: {
path: './seeders', // path to the folder with seeders
pathTs: undefined, // path to the folder with TS seeders (if used, we should put path to compiled files in `path`)
defaultSeeder: 'DatabaseSeeder', // default seeder class name
glob: '!(*.d).{js,ts}', // how to match seeder files (all .js and .ts files, but not .d.ts)
emit: 'ts', // seeder generation mode
fileName: (className: string) => className, // seeder file naming convention
},
});
我们还可以使用 环境变量 覆盖这些默认值:
¥We can also override these default using the environment variables:
-
MIKRO_ORM_SEEDER_PATH
-
MIKRO_ORM_SEEDER_PATH_TS
-
MIKRO_ORM_SEEDER_EMIT
-
MIKRO_ORM_SEEDER_GLOB
-
MIKRO_ORM_SEEDER_DEFAULT_SEEDER
。
种子
¥Seeders
种子类包含一个方法 run
。当你使用命令 npx mikro-orm seeder:run
时,将调用此方法。在 run
方法中,你可以定义如何以及要将哪些数据插入数据库。你可以使用 EntityManager 创建实体,也可以使用 工厂。
¥A seeder class contains one method run
. This method is called when you use the command npx mikro-orm seeder:run
. In the run
method you define how and what data you want to insert into the database. You can create entities using the EntityManager or you can use Factories.
你可以使用以下 CLI 命令创建自己的种子类:
¥You can create your own seeder classes using the following CLI command:
npx mikro-orm seeder:create DatabaseSeeder # generates the class DatabaseSeeder
npx mikro-orm seeder:create test # generates the class TestSeeder
npx mikro-orm seeder:create project-names # generates the class ProjectNamesSeeder
这将创建一个新的种子类。默认情况下,它将在 ./seeders/
目录中生成。你可以使用密钥 seeder.path
或使用 环境变量 MIKRO_ORM_SEEDER_PATH
在配置中配置目录。你可以使用名称、类名或带连字符的名称调用 seeder:create
命令。
¥This creates a new seeder class. By default, it will be generated in the ./seeders/
directory. You can configure the directory in the config with the key seeder.path
or using the environment variable MIKRO_ORM_SEEDER_PATH
. You are allowed to call the seeder:create
command with a name, class name or hyphenated name.
作为示例,我们将查看一个非常基本的种子。
¥As an example we will look at a very basic seeder.
请注意,种子中可用的
EntityManager
将启用persistOnCreate
(即使你在 ORM 配置中明确禁用它),因此调用em.create()
将自动在创建的实体上调用em.persist()
。如果我们改用实体构造函数,则需要明确调用em.persist()
。¥Note that the
EntityManager
available in seeders will havepersistOnCreate
enabled (even if you explicitly disable it in the ORM config), hence callingem.create()
will automatically callem.persist()
on the created entity. If we use entity constructor instead, we need to callem.persist()
explicitly.
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> {
// will get persisted automatically
const author = em.create(Author, {
name: 'John Snow',
email: 'snow@wall.st'
});
// but if we would do `const author = new Author()` instead,
// we would need to call `em.persist(author)` explicitly.
}
}
从命令行或以编程方式运行种子程序将在
run
方法完成后自动调用flush
和clear
。¥Running a seeder from the command line or programmatically will automatically call
flush
andclear
after therun
method has completed.
使用实体工厂
¥Using entity factories
你也可以使用 实体工厂,而不必为每个实体指定所有属性。这些可用于生成大量数据库记录。请阅读 关于如何定义工厂的文档 以了解如何定义你的工厂。
¥Instead of specifying all the attributes for every entity, you can also use entity factories. These can be used to generate large amounts of database records. Please read the documentation on how to define factories to learn how to define your factories.
作为示例,我们将生成 10 位作者。
¥As an example we will generate 10 authors.
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);
}
}
调用其他种子程序
¥Calling additional seeders
在 run
方法内部,你可以指定其他种子类。你可以使用 call
方法将数据库种子分成多个文件,以防止种子文件变得太大。call
方法需要 em
和种子类数组。
¥Inside the run
method you can specify other seeder classes. You can use the call
method to break up the database seeder into multiple files to prevent a seeder file from becoming too large. The call
method requires an em
and an array of seeder classes.
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,
]);
}
}
共享上下文
¥Shared context
通常,我们可能希望生成引用其他种子创建的其他实体的实体。为此,我们可以使用第二个参数或 run
方法中提供的共享上下文对象。使用 this.call()
时会自动创建它,并传递给每个种子的 run
方法。让我们看看上一个示例中的 AuthorSeeder
和 BookSeeder
是什么样子:
¥Often we might want to generate entities that are referencing other entities, created by other seeders. For that we can use the shared context object provided in the second parameter or run
method. It is automatically created when using this.call()
and passed down to each seeder's run
method. Let's see how the AuthorSeeder
and BookSeeder
from previous example could look like:
export class AuthorSeeder extends Seeder {
async run(em: EntityManager, context: Dictionary): Promise<void> {
// save the entity to the context
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, // use the entity from context
});
}
}
实体工厂
¥Entity factories
在测试时,你可以在开始测试之前将实体插入数据库中。你也可以使用 Factory
来使用实体工厂为实体定义一组默认属性,而不必手动指定每个实体的每个属性。
¥When testing you may insert entities in the database before starting a test. Instead of specifying every attribute of every entity by hand, you could also use a Factory
to define a set of default attributes for an entity using entity factories.
让我们看一个 作者实体 的示例工厂。
¥Let's have a look at an example factory for an Author entity.
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),
};
}
}
在这里我们扩展了基本 Factory
类,定义了一个 model
属性和一个 definition
方法。model
定义工厂为哪个实体生成实体实例。definition
方法返回使用工厂创建实体时应应用的默认属性值集。
¥Here we extend the base Factory
class, define a model
property and a definition
method. The model
defines for which entity the factory generates entity instances. The definition
method returns the default set of attribute values that should be applied when creating an entity using the factory.
在此示例中,我们使用一个名为 Faker 的库,它允许你方便地生成各种随机数据以进行测试。
¥In this example, we use a library called Faker, which allows you to conveniently generate various kinds of random data for testing.
自 v6 起,Faker 不再从种子包中重新导出,需要单独安装。
¥Since v6, Faker is no longer re-exported from the seeder package and needs to be installed separately.
使用创建实体工厂
¥Creating entities using factories
定义工厂后,你可以使用它们来生成实体。可以使用此查询创建简单表:
¥Once you defined your factories you can use them to generate entities. Simply import the factory, instantiate it and call the makeOne
method.
const author = new AuthorFactory(orm.em).makeOne();
生成多个实体
¥Generate multiple entities
通过调用 make
方法生成多个实体。make
方法的参数是你生成的实体数。
¥Generate multiple entities by calling the make
method. The parameter of the make
method is the number of entities you generate.
// Generate 5 authors
const authors = new AuthorFactory(orm.em).make(5);
覆盖属性
¥Overriding attributes
如果你想覆盖工厂的某些默认值,可以将对象传递给 make
方法。仅替换指定的属性,而其余属性仍设置为工厂指定的默认值。
¥If you would like to override some of the default values of your factories, you may pass an object to the make
method. Only the specified attributes will be replaced while the rest of the attributes remain set to their default values as specified by the factory.
// Make a single author
const author = new AuthorFactory(orm.em).makeOne({
name: 'John Snow',
});
// Make 5 authors
const authors = new AuthorFactory(orm.em).make(5, {
name: 'John Snow',
});
持久化实体
¥Persisting entities
create
方法使用 EntityManager 的 persistAndFlush
方法实例化实体并将其持久化到数据库。
¥The create
method instantiates entities and persists them to the database using the persistAndFlush
method of the EntityManager.
// Make and persist a single author
const author = await new AuthorFactory(orm.em).createOne();
// Make and persist 5 authors
const authors = await new AuthorFactory(orm.em).create(5);
你可以通过将对象传递给 create
方法来覆盖工厂的默认值。
¥You can override the default values of your factories by passing an object to the create
method.
// Make and persist a single author
const author = await new AuthorFactory(orm.em).createOne({
name: 'John Snow',
});
// Make and persist a 5 authors
const authors = await new AuthorFactory(orm.em).create(5, {
name: 'John Snow',
});
工厂关系
¥Factory relationships
为一个实体创建大量数据是很好的,但大多数时候我们希望为多个实体创建数据,并且这些实体之间也有关系。为此,我们可以使用可以链接到工厂的 each
方法。可以使用转换工厂输出实体然后再返回的函数来调用 each
方法。让我们看一些不同关系的例子。
¥It is nice to create large quantities of data for one entity, but most of the time we want to create data for multiple entities and also have relations between these. For this we can use the each
method which can be chained on a factory. The each
method can be called with a function that transforms output entity from the factory before returning it. Let's look at some examples for the different relations.
多对一和一对一关系
¥ManyToOne and OneToOne relations
const books: Book[] = new BookFactory(orm.em).each(book => {
book.author = new AuthorFactory(orm.em).makeOne();
}).make(5);
一对多和多对多
¥OneToMany and ManyToMany
const books: Book[] = new BookFactory(orm.em).each(book => {
book.owners.set(new OwnerFactory(orm.em).make(5));
}).make(5);
与 CLI
¥Use with CLI
你可以执行 seeder:run
MikroORM CLI 命令来为你的数据库播种。默认情况下,seeder:run
命令运行 DatabaseSeeder
类,而该类又可能调用其他种子类。你可以使用密钥 seeder.defaultSeeder
或使用 环境变量 MIKRO_ORM_SEEDER_DEFAULT_SEEDER
配置默认种子。你还可以使用 --class
选项指定种子类:
¥You may execute the seeder:run
MikroORM CLI command to seed your database. By default, the seeder:run
command runs the DatabaseSeeder
class, which may in turn invoke other seed classes. You can configure the default seeder using the key seeder.defaultSeeder
or using the environment variable MIKRO_ORM_SEEDER_DEFAULT_SEEDER
. You can also use the --class
option to specify a seeder class:
npx mikro-orm seeder:run
npx mikro-orm seeder:run --class=BookSeeder
你还可以使用 migrate:fresh
或 schema:fresh
命令结合 --seed
选项来播种数据库,这将删除所有表并重新运行所有迁移或根据当前实体生成数据库。此命令对于完全重建数据库很有用:
¥You may also seed your database using the migrate:fresh
or schema:fresh
command in combination with the --seed
option, which will drop all tables and re-run all of your migrations or generate the database based on the current entities. This command is useful for completely re-building your database:
npx mikro-orm migration:fresh --seed # will drop the database, run all migrations and the DatabaseSeeder class
npx mikro-orm schema:fresh --seed # will recreate the database and run the DatabaseSeeder class
如果你不想运行 DatabaseSeeder
类,你可以指定默认的种子类应该是什么。这可以通过更改 默认设置 来完成。另一个选项是明确定义要运行的种子类。
¥If you do not want to run the DatabaseSeeder
class you can either specify what the default seeder class should be. This can be done by changing the default settings. Another option is to explicitly define the seeder class you want to run.
npx mikro-orm migration:fresh --seed TestSeeder # will drop the database, run all migrations and the TestSeeder class
npx mikro-orm schema:fresh --seed ProjectsSeeder # will recreate the database and run the ProjectsSeeder class
在测试中使用
¥Use in tests
现在我们知道如何创建种子和工厂,但是我们如何在测试中有效地使用它们。我们将展示一个如何使用它的示例。
¥Now we know how to create seeders and factories, but how can we effectively use them in tests. We will show an example how it can be used.
beforeAll(async () => {
// Get seeder from MikroORM
const seeder = orm.getSeeder();
// Refresh the database to start clean (work in mongo too since v5)
await orm.schema.refreshDatabase();
// Seed using a seeder defined by you
await seeder.seed(DatabaseSeeder);
});
test(() => {
// Do tests
});
afterAll(async () => {
// Close connection
await orm.close();
});
在生产中运行种子
¥Running seeder in production
在生产环境中,我们可能想要使用编译的种子文件。我们需要做的就是相应地配置种子路径:
¥In production environment we might want to use compiled seeder files. All we need to do is to configure the seeder path accordingly:
import { MikroORM, Utils } from '@mikro-orm/core';
await MikroORM.init({
seeder: {
path: 'dist/seeders',
pathTs: 'src/seeders',
},
// or alternatively
// seeder: {
// path: Utils.detectTsNode() ? 'src/seeders' : 'dist/seeders',
// },
// ...
});
这应该允许使用 CLI 生成 TS 种子文件(因为在 CLI 中我们可能已启用 TS 支持),同时在生产中使用编译的 JS 文件,其中 ts-node 未注册。
¥This should allow using CLI to generate TS seeder files (as in CLI we probably have TS support enabled), while using compiled JS files in production, where ts-node is not registered.