CRUD 操作
本指南涵盖 Lucid 模型的 CRUD API。你将学会如何:
- 单条或批量创建记录
- 使用
find、findBy、first及其orFail变体读取记录 - 通过先获取再保存模式或使用查询构建器更新记录
- 逐条或批量删除记录
- 使用 find-or-create 和 update-or-create 辅助方法
- 使用
Quietly变体跳过生命周期钩子
概述
Lucid 模型将 CRUD 操作作为类上的静态方法和已加载模型的实例方法公开。静态方法处理常见场景(Model.create、Model.findOrFail、Model.updateOrCreate);实例方法操作已加载的行(user.save、user.delete)。通过 Model.query() 访问的模型查询构建器处理所有不适合单方法调用的查询。完整参考请参见模型查询构建器。
创建记录
create
通过传入列值对象来持久化新记录。该方法插入一行,运行每个已配置的钩子,并返回模型实例。
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
export default class UsersController {
async store({ request }: HttpContext) {
const user = await User.create({
email: request.input('email'),
password: request.input('password'),
})
return user
}
}
先构建实例
创建实例,逐个赋值列,然后通过 save 持久化。第一次 save 调用执行 INSERT;后续 save 调用执行 UPDATE。
const user = new User()
user.email = 'virk@adonisjs.com'
user.password = 'secret'
await user.save()
fill 和 merge 可一次在未保存或已加载的实例上设置多个属性。它们的行为不同:fill 在设置新值之前会完全替换模型的属性,而 merge 仅更新你传入的键,其余保持不变。
const user = new User()
// 保存前设置多个属性的等效方式
await user.fill({ email, password }).save()
await user.merge({ email, password }).save()
// 在已加载的实例上,通常你需要的是 merge
const existing = await User.findOrFail(1)
existing.merge({ email: newEmail }).save()
当你想在赋新值之前完全重置实例时使用 fill。当你想更新列的子集并保持实例其余状态不变时使用 merge。
createMany
持久化一组记录。createMany 开启一个托管事务,逐行插入(因此每个模型的钩子和时间戳都会运行),并一起提交整批数据。如果任何插入失败,事务会回滚。
const users = await User.createMany([
{ email: 'virk@adonisjs.com', password: 'secret' },
{ email: 'romain@adonisjs.com', password: 'secret' },
])
由于每行都是单独插入的,总查询数随批量大小增长。对于不需要钩子的大规模导入,改用 insert 查询构建器中的 db.table('users').multiInsert(...),它只生成一条语句。
allowExtraProperties
默认情况下,传入模型未声明的键会抛出错误。设置 allowExtraProperties: true 以静默丢弃未知键。当转发可能包含客户端字段的 HTTP 请求载荷时很有用,这些字段不会映射到数据库。
await User.create(request.all(), { allowExtraProperties: true })
// 或在实例上
user.fill(request.all(), true).save()
createQuietly 和 createManyQuietly
Quietly 变体在不运行钩子的情况下插入记录。当必须跳过钩子时使用,例如在预计算钩子本应生成的值的种子器中,或在测试 fixture 设置期间。
await User.createQuietly({ email, password })
await User.createManyQuietly([
{ email: 'a@example.com', password: 'secret' },
{ email: 'b@example.com', password: 'secret' },
])
跳过钩子仅适用于插入本身。你在回调中触发的关联模型操作仍然会运行自己的钩子。
读取记录
all
从表中获取每一行。
const users = await User.all()
// SQL: SELECT * FROM "users" ORDER BY "id" DESC
find 和 findOrFail
按主键查找行。find 在没有匹配行时返回 null;findOrFail 抛出 E_ROW_NOT_FOUND(AdonisJS 将其转换为 404 响应)。
const user = await User.find(1) // User | null
const user = await User.findOrFail(1) // User,否则 404
findBy 和 findByOrFail
按任意列查找行。支持两种调用形式:列 + 值,或用于复合查找的列值对对象。
const user = await User.findBy('email', 'virk@adonisjs.com')
const user = await User.findBy({
email: 'virk@adonisjs.com',
is_active: true,
})
findByOrFail 有相同的两种签名,在没有匹配行时抛出异常。
const user = await User.findByOrFail('email', 'virk@adonisjs.com')
findMany 和 findManyBy
findMany 按主键加载多行。findManyBy 加载匹配一个或多个列的多行(与 findBy 相同的两种签名)。
const users = await User.findMany([1, 2, 3])
const activeUsers = await User.findManyBy('is_active', true)
const activeAdmins = await User.findManyBy({
is_active: true,
role: 'admin',
})
findMany 不保留输入数组的顺序。返回的行按主键排序(降序)。不要假设 findMany([2, 1]) 会先返回 id = 2 的行。当顺序很重要时,使用 Model.query().whereIn(...).orderBy(...) 构建查询,或在应用代码中对结果排序。
first 和 firstOrFail
从表中获取第一行。没有查询约束时,这在大多数方言上返回主键最小的行。
const user = await User.first() // User | null
const user = await User.firstOrFail() // User,否则抛出异常
使用查询构建器
静态查找器覆盖了常见场景。对于更复杂的需求(排序、连接、作用域、预加载、条件过滤),使用模型查询构建器。
const users = await User
.query()
.where('is_active', true)
.orWhereNotNull('subscription_plan')
.orderBy('created_at', 'desc')
const user = await User
.query()
.where('email', email)
.firstOrFail()
完整 API 请参见模型查询构建器指南,包括预加载、聚合和查询作用域。
更新记录
获取、修改、保存
标准模式是加载行,更改你要更新的属性,然后调用 save。Lucid 通过 $dirty 跟踪哪些列发生了变化,只写入这些列。
const user = await User.findOrFail(params.id)
user.email = request.input('email')
await user.save()
当你想一次应用多个更改时使用 merge + save。
await user.merge({
email: request.input('email'),
lastLoginAt: DateTime.now(),
}).save()
saveQuietly
在不运行 beforeSave、afterSave、beforeUpdate 或 afterUpdate 钩子的情况下保存。在填充种子、恢复备份或更新不应触发钩子副作用的列时使用。
user.lastSyncedAt = DateTime.now()
await user.saveQuietly()
使用查询构建器批量更新
当你需要一次修改大量行时(标记过期会话、归档旧行、重置标志),使用模型查询构建器的 update。它发出一条 UPDATE 语句并跳过模型钩子和自动时间戳。
await User.query().where('is_active', false).update({
status: 'archived',
archived_at: DateTime.now().toSQL(),
})
由于此路径绕过模型钩子,你的钩子强制执行的任何不变量(密码哈希、审计日志插入、计算列)都不会运行。当每行都需要完整的模型管道时使用实例模式;当行仅由 SQL 管理时使用查询构建器路径。
删除记录
实例删除
加载行并调用 delete。实例的 $isDeleted 标志变为 true。
const user = await User.findOrFail(params.id)
await user.delete()
deleteQuietly
在不运行 beforeDelete 或 afterDelete 钩子的情况下删除。
await user.deleteQuietly()
使用查询构建器批量删除
对于不应在每行上运行钩子的删除,通过查询构建器发出删除。
await User.query().where('is_verified', false).delete()
与批量更新相同的注意事项适用:钩子和自动时间戳列会被跳过。
Upsert 和 find-or-create 辅助方法
这些静态方法将查找与条件创建或更新结合在一起。它们都接受一个单独的搜索载荷(用于查找现有行的键)和一个可选的保存载荷(创建新行或更新现有行时写入的值)。
firstOrCreate
查找匹配搜索载荷的行。没有匹配行时,使用合并的搜索和保存载荷创建一行。返回现有的或新创建的模型。
const user = await User.firstOrCreate(
{ email: 'virk@adonisjs.com' }, // 搜索
{ password: 'secret' } // 创建时的额外值
)
如果该邮箱的用户已存在,则原样返回,password 被忽略。如果没有匹配,Lucid 插入一行同时包含邮箱和密码。
firstOrNew
与 firstOrCreate 相同的查找,但当没有匹配行时,Lucid 返回一个新的未保存实例,包含合并的载荷。当你想在持久化之前做更多修改,或决定是否保存时很有用。
const user = await User.firstOrNew(
{ email: 'virk@adonisjs.com' },
{ password: 'secret' }
)
if (user.$isNew) {
user.source = 'invitation'
await user.save()
}
updateOrCreate
查找匹配搜索载荷的行并用保存载荷更新它,或在没有匹配时用合并的载荷插入新行。
await User.updateOrCreate(
{ email: 'virk@adonisjs.com' },
{ lastLoginAt: DateTime.now() }
)
与 firstOrCreate 不同,updateOrCreate 始终写入数据库,要么是 INSERT 语句,要么是 UPDATE 语句。
fetchOrCreateMany
firstOrCreate 的批量版本。传入标识行的唯一键(或键数组),加上一组对象。对于每个对象,Lucid 按键查找现有行,没有匹配行时插入。现有行原样返回不变。
await User.fetchOrCreateMany('email', [
{ email: 'foo@example.com', name: 'Foo' },
{ email: 'bar@example.com', name: 'Bar' },
])
复合键使用数组:
await Post.fetchOrCreateMany(['tenant_id', 'slug'], [
{ tenant_id: 1, slug: 'hello', body: '...' },
{ tenant_id: 1, slug: 'world', body: '...' },
])
fetchOrNewUpMany
与 fetchOrCreateMany 相同,但不存在的行作为未保存的实例返回而非插入。对于预览流程或将持久化延迟到显式确认之后很有用。
const users = await User.fetchOrNewUpMany('email', [
{ email: 'foo@example.com', name: 'Foo' },
{ email: 'bar@example.com', name: 'Bar' },
])
// 部分项可能是未保存的实例
users.filter((user) => user.$isNew)
updateOrCreateMany
updateOrCreate 的批量版本。每个对象按给定键匹配。现有行被更新;缺失的行被插入。
await User.updateOrCreateMany('email', [
{ email: 'foo@example.com', name: 'Foo' },
{ email: 'bar@example.com', name: 'Bar' },
])
复合键在这里也适用,通常用于同步父 + 子组合。
await SubscriptionDay.updateOrCreateMany(['subscription_id', 'day'], days)
所有四个批量辅助方法(fetchOrCreateMany、fetchOrNewUpMany、updateOrCreateMany、createMany)都将写入操作包装在托管事务中,因此批量操作是全有或全无的。
清空表
Model.truncate 清空底层表。在测试设置和拆卸中使用它来重置到干净状态。在支持的方言上(PostgreSQL、MSSQL)传入 true 以级联清空关联表。
await User.truncate()
await User.truncate(true) // 级联
Truncate 绕过模型钩子,不会触发 beforeDelete 或 afterDelete。它比 DELETE FROM users 更快,因为大多数数据库会重置自增计数器并跳过逐行日志记录,但两者并非在所有方言中都等价。