Transactions

数据库事务(Database transactions)允许你的应用程序以"全有或全无"的方式提交一系列数据库变更。考虑这样一个 HTTP 请求:它创建一个新的订单,并通过 afterChange 钩子更新相关商品的库存数量。如果在更新某个商品时发生错误并向用户返回 HTTP 错误,你肯定不希望新的订单被持久化,也不希望其他商品被修改。这种与数据库的交互可以通过事务无缝处理。

默认情况下,只要配置的数据库支持,Payload 会对所有数据变更操作使用事务。所有数据库变更都被包含在 Payload 操作中,任何抛出的错误都会导致所有变更被回滚而不会被提交。当数据库不支持事务时,Payload 会继续按预期运行,只是不使用事务功能。

注意:

MongoDB 需要使用副本集(replicaset)连接才能支持事务功能。

注意:

SQLite 默认禁用事务。你需要传递 transactionOptions: {} 来启用它们。

对 Payload 的初始请求会启动一个新事务,并将其附加到 req.transactionID 上。如果你的 hook 需要与数据库交互,可以通过在参数中传递 req 来选择使用同一个事务。例如:

const afterChange: CollectionAfterChangeHook = async ({ req }) => {
  // 由于 req.transactionID 由 Payload 分配并传递,
  // my-slug 只会在整个请求成功时才会被持久化
  await req.payload.create({
    req,
    collection: 'my-slug',
    data: {
      some: 'data',
    },
  })
}

异步钩子与事务处理

由于 Payload 钩子可以是异步的,并且可以不等待结果就执行,这可能导致在请求回滚时返回错误的成功响应。如果你的钩子中没有 await 结果,那么你不应该传递 req.transactionID

const afterChange: CollectionAfterChangeHook = async ({ req }) => {
  // 警告:使用相同的 req 进行异步调用但不等待结果,
  // 可能会导致失败时返回 OK 响应,而实际数据并未提交
  const dangerouslyIgnoreAsync = req.payload.create({
    req,
    collection: 'my-slug',
    data: {
      some: 'other data',
    },
  })

  // 如果此调用失败,它不会回滚其他更改,
  // 因为没有传递 req(及其 transactionID)
  const safelyIgnoredAsync = req.payload.create({
    collection: 'my-slug',
    data: {
      some: 'other data',
    },
  })
}

直接事务访问

在编写自定义脚本或端点时,你可能希望直接控制事务。这对于在 Payload 的 Local API 之外与数据库交互非常有用。

以下函数可用于管理事务:

  • payload.db.beginTransaction - 启动新会话并返回事务 ID,用于其他 Payload Local API 调用
  • payload.db.commitTransaction - 接收事务标识符,提交所有更改
  • payload.db.rollbackTransaction - 接收事务标识符,回滚所有更改

Payload 使用 req 对象将事务 ID 传递给数据库适配器。如果不使用 req 对象,可以创建新对象直接将事务 ID 传递给数据库适配器方法和 Local API 调用。

示例:

import payload from 'payload'
import config from './payload.config'

const standalonePayloadScript = async () => {
  // 初始化 Payload
  await payload.init({ config })

  const transactionID = await payload.db.beginTransaction()

  try {
    // 使用 Local API 进行更新
    await payload.update({
      collection: 'posts',
      data: {
        some: 'data',
      },
      where: {
        slug: { equals: 'my-slug' },
      },
      req: { transactionID },
    })

    /*
      你可以进行额外的数据库更改或运行其他函数
      这些操作需要以"全有或全无"的方式提交
     */

    // 提交事务
    await payload.db.commitTransaction(transactionID)
  } catch (error) {
    // 回滚事务
    await payload.db.rollbackTransaction(transactionID)
  }
}

standalonePayloadScript()

禁用事务

如果你想完全禁用事务,可以在数据库适配器配置中将 transactionOptions 设为 false。所有官方的 Payload 数据库适配器都支持此选项。

除了允许在适配器级别禁用数据库事务外,你还可以通过在 Local API 的直接调用中添加 disableTransaction: true 参数来阻止 Payload 使用事务。例如:

await payload.update({
  collection: 'posts',
  data: {
    some: 'data',
  },
  where: {
    slug: { equals: 'my-slug' },
  },
  disableTransaction: true,
})