数据库种子

数据库种子

本指南涵盖数据库种子。你将学会如何:

  • 创建种子并通过 Ace 运行它
  • 将种子限制到特定环境
  • 编写可以在每次启动时安全运行的幂等种子
  • 针对特定数据库连接
  • 控制种子运行的顺序

概述

种子是一个向数据库插入数据的类。用于随应用一起发布的参考表(国家、货币、权限)、本地开发测试数据,以及需要一致起始状态的测试。

种子存储在 database/seeders/ 内。使用 make:seeder Ace 命令生成一个。

node ace make:seeder User
// CREATE: database/seeders/user_seeder.ts

每个种子继承 BaseSeeder 并实现异步 run 方法。在 run 内部,使用 Lucid 模型、数据库查询构建器或原始 SQL。种子不关心你使用哪种工具。

database/seeders/user_seeder.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'
export default class UserSeeder extends BaseSeeder {
async run() {
await User.createMany([
{ email: 'virk@adonisjs.com', password: 'secret' },
{ email: 'romain@adonisjs.com', password: 'supersecret' },
])
}
}

运行种子

使用 db:seed 命令运行 database/seeders 中的每个种子。

node ace db:seed

传入 --files 一次或多次以仅运行特定的种子文件。使用完整路径以便你的 shell 自动补全可以帮助。

node ace db:seed --files "./database/seeders/user_seeder.ts"

使用 --interactive 在提示符处交互式选择种子。

node ace db:seed -i

参见命令参考了解完整的标志列表,包括 --connection--compact-output

环境特定种子

在种子上设置 static environment 属性以将其限制到一个或多个环境。种子仅在应用的环境与列出的值之一匹配时运行。

import { BaseSeeder } from '@adonisjs/lucid/seeders'
export default class UserSeeder extends BaseSeeder {
static environment = ['development', 'test']
async run() {}
}

没有 environment 时,种子在每个环境中运行。使用该标志将开发测试数据和测试数据排除在生产运行之外。

幂等种子

Lucid 不跟踪哪些种子已经运行。调用 db:seed 两次会执行每个种子两次,这对于每次运行都创建新行的种子没问题,但对于应该只存在一次的参考数据是错误的。

对于参考数据,使用 updateOrCreateManyfetchOrCreateMany 使种子幂等。Lucid 通过你提供的唯一键查找现有行并仅插入缺失的行。

database/seeders/country_seeder.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import Country from '#models/country'
export default class CountrySeeder extends BaseSeeder {
async run() {
await Country.updateOrCreateMany('isoCode', [
{ isoCode: 'IN', name: 'India' },
{ isoCode: 'FR', name: 'France' },
{ isoCode: 'TH', name: 'Thailand' },
])
}
}

重复运行此种子永远不会产生重复项。第一次运行插入三行;后续运行用提供的值更新现有行并插入任何新条目。

针对特定连接

db:seed 命令接受 --connection 标志。每个种子接收匹配的查询客户端作为 this.client,你可以通过其静态方法上的 client 选项将其转发给 Lucid 模型。

database/seeders/user_seeder.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'
export default class UserSeeder extends BaseSeeder {
async run() {
await User.create(
{ email: 'virk@adonisjs.com', password: 'secret' },
{ client: this.client }
)
}
}

使用命令标志针对特定连接运行种子。

node ace db:seed --connection=tenant-a

this.client 是完整的 QueryClientContract,因此你也可以降级到数据库查询构建器以在同一连接上执行原始操作。

await this.client.table('users').insert({ email: 'virk@adonisjs.com' })

种子配置

种子配置位于 config/database.ts 中连接的 seeders 对象下。

config/database.ts
{
postgres: {
client: 'pg',
seeders: {
paths: ['./database/seeders'],
naturalSort: true,
},
},
}

参见配置指南了解完整的字段参考。

控制种子顺序

db:seed 按文件系统返回的顺序运行种子,这不能保证与你关心的顺序匹配。参考表通常需要在引用它们的表之前运行。

两种模式可以解决这个问题。选择适合项目的任何一种。

为文件名编号

在种子文件名前加上计数器前缀,以便字母排序产生正确的顺序。

database/seeders/
01_category_seeder.ts
02_user_seeder.ts
03_post_seeder.ts

这无需任何配置即可工作,并且从文件系统读取时很清晰。

使用主种子

对于更复杂的排序(条件种子、共享设置),创建一个种子来按你想要的顺序导入和调用其他种子,然后配置 db:seed 仅运行此种子。

创建主种子。

node ace make:seeder main/index
// CREATE: database/seeders/main/index_seeder.ts

将种子配置指向 main 目录,以便 db:seed 仅运行索引种子。

config/database.ts
{
postgres: {
client: 'pg',
seeders: {
paths: ['./database/seeders/main'],
},
},
}

在索引内部导入和调用每个种子。使用 app.nodeEnvironment 自己尊重每个种子的 environment 声明,因为运行器仅在直接调用顶级种子(索引)时应用 environment 检查。

database/seeders/main/index_seeder.ts
import app from '@adonisjs/core/services/app'
import { BaseSeeder } from '@adonisjs/lucid/seeders'
export default class IndexSeeder extends BaseSeeder {
private async seed(SeederModule: { default: typeof BaseSeeder }) {
const Seeder = SeederModule.default
if (Seeder.environment && !Seeder.environment.includes(app.nodeEnvironment)) {
return
}
await new Seeder(this.client).run()
}
async run() {
await this.seed(await import('#database/seeders/category_seeder'))
await this.seed(await import('#database/seeders/user_seeder'))
await this.seed(await import('#database/seeders/post_seeder'))
}
}

像往常一样运行种子。只有索引种子匹配配置的路径,它协调其余的种子。

node ace db:seed