You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为每个GraphQL请求实现数据库事务

实现Apollo GraphQL请求对应单次PostgreSQL事务(基于Prisma)

核心思路

通过Apollo的**请求上下文(Context)**绑定Prisma交互式事务,让单个GraphQL请求中所有的mutation/query操作都共享同一个数据库事务:

  1. 请求进入时,启动Prisma交互式事务,将事务实例注入上下文
  2. 所有Resolver都使用上下文里的事务实例执行数据库操作
  3. 请求正常完成时提交事务,出现任何错误则回滚事务

完整代码示例

1. 依赖准备

确保已安装必要依赖:

npm install @apollo/server graphql prisma @prisma/client

2. Prisma配置(schema.prisma)

确保你的Prisma schema配置了PostgreSQL:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// 示例业务模型
model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
}

model Post {
  id     Int     @id @default(autoincrement())
  title  String
  author User    @relation(fields: [authorId], references: [id])
  authorId Int
}

3. Apollo服务器配置(带事务上下文)

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { PrismaClient, Prisma } from '@prisma/client';

const prisma = new PrismaClient();

// GraphQL Schema定义
const typeDefs = `
  type User {
    id: Int!
    name: String!
    email: String!
  }

  type Post {
    id: Int!
    title: String!
    author: User!
  }

  type Mutation {
    createUserWithPost(name: String!, email: String!, postTitle: String!): Post!
  }
`;

// Resolvers:所有数据库操作必须使用上下文的事务实例
const resolvers = {
  Mutation: {
    createUserWithPost: async (_, args, context) => {
      const user = await context.tx.user.create({
        data: {
          name: args.name,
          email: args.email,
        },
      });

      const post = await context.tx.post.create({
        data: {
          title: args.postTitle,
          authorId: user.id,
        },
        include: { author: true },
      });

      return post;
    },
  },
};

// 创建请求上下文:初始化交互式事务
async function createContext() {
  const tx = await prisma.$transaction(async (tx) => {
    // 仅启动事务,返回事务实例供resolver使用
    return tx;
  }, {
    // 可选:配置事务隔离级别(PostgreSQL支持的级别)
    isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted,
  });

  return { tx };
}

// 启动Apollo服务器,配置事务生命周期管理
async function startServer() {
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [{
      async requestDidStart() {
        return {
          async willSendResponse({ context, errors }) {
            if (errors) {
              // 请求出错时主动回滚事务
              await context.tx.$rollback();
            }
            // 无错误时Prisma交互式事务会自动提交,无需额外操作
          },
        };
      },
    }],
  });

  const { url } = await startStandaloneServer(server, {
    context: async () => createContext(),
  });

  console.log(`🚀 Server ready at ${url}`);
}

startServer().catch((err) => console.error(err));

关键细节

  • 事务实例唯一性:所有Resolver必须使用context.tx执行数据库操作,不能用全局的prisma客户端,否则操作会脱离当前事务。
  • 错误处理:Prisma交互式事务默认会在回调抛出错误时自动回滚,结合Apollo的willSendResponse插件,可覆盖所有请求级错误场景,确保事务可靠回滚。
  • TypeScript类型支持:建议给上下文定义类型,避免类型报错:
    type Context = {
      tx: Prisma.TransactionClient;
    };
    
    // 初始化Apollo时指定类型
    const server = new ApolloServer<Context>({ typeDefs, resolvers, plugins });
    
  • 隔离级别调整:根据业务需求选择合适的事务隔离级别,PostgreSQL支持ReadUncommitted、ReadCommitted(默认)、RepeatableRead、Serializable。

替代方案

  1. Apollo Plugin全托管事务:把事务的创建、提交/回滚逻辑完全放到插件中,上下文仅存储事务实例,代码结构更简洁:
    plugins: [{
      async requestDidStart() {
        let tx: Prisma.TransactionClient;
        try {
          tx = await prisma.$transaction(async (tx) => tx);
          return {
            async willSendResponse({ errors }) {
              if (errors) await tx.$rollback();
            },
            async context() {
              return { tx };
            },
          };
        } catch (err) {
          console.error('事务初始化失败', err);
          throw err;
        }
      },
    }],
    
  2. 不推荐的方案:避免用Prisma Middleware绑定请求级事务,因为middleware无法准确识别GraphQL请求的边界,容易导致事务泄漏或误绑定。

内容的提问来源于stack exchange,提问作者Peter Coppens

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 17:36:19