技术篇:数据库-Prisma

·907 字·5 分钟
独立开发 Next.js
Next.js实战教程 - 系列文章
Part 9: 当前文章

学习目标:为应用接入数据库存储

Prisma 简介 #

Prisma 是一个现代化的 ORM(对象关系映射)工具,支持 TypeScript 和 JavaScript,旨在简化数据库操作,提高开发效率。支持多种数据库,包括 PostgreSQL、MySQL、SQLite 和 MongoDB,同时 Prisma 很容易与 Next.js、NestJS 等框架集成,简化全栈开发。本文将详细介绍如何在 Next.js 项目中接入 Prisma,并使用其进行数据库操作。

项目初始化 #

npx create-next-app@latest

 What is your project named?  example-prisma
 Would you like to use TypeScript?  No / Yes
 Would you like to use ESLint?  No / Yes
 Would you like to use Tailwind CSS?  No / Yes
 Would you like to use `src/` directory?  No / Yes
 Would you like to use App Router? (recommended)  No / Yes
 Would you like to customize the default import alias (@/*)?  No / Yes

image.png

安装和配置 Prisma #

安装 Prisma CLI

yarn add prisma --dev
  • Prisma CLI 是用于初始化 Prisma 设置、生成 Prisma 客户端、以及执行数据库迁移的命令行工具。

安装 Prisma Client

 yarn add @prisma/client
  • Prisma Client 是 Prisma 提供的一个自动生成的、类型安全的数据库查询工具。它的主要作用是帮助开发者与数据库进行交互,而不需要编写原始 SQL 语句。

安装 Prisma CLI 之后,可以通过以下命令初始化 Prisma 配置

yarn prisma init

此命令将在你的项目根目录下创建一个 prisma/ 目录,里面包含一个 schema.prisma 文件,以及一个 .env 文件。schema.prisma 是你定义数据库模型的地方,而 .env 文件则用于存储数据库连接信息。

配置数据库连接 #

prisma/schema.prisma 文件中,你需要设置数据库链接,以 MySQL 为例配置如下所示:

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

.env 文件中配置你的数据库连接:

DATABASE_URL="mysql://root:123456@localhost:3306/example"

你可以根据使用的数据库类型更改此连接。Prisma 支持多种数据库,每种数据库的连接字符串格式可能有所不同。

定义数据模型 #

prisma/schema.prisma 文件中,你可以定义数据库模型。例如,定义一个简单的用户模型:

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

这个模型定义了一个 User 表,其中包含一个自增的 id、唯一的 email、可选的 name、以及自动记录的 createdAtupdatedAt 时间戳。

生成迁移并应用到数据库(可选) #

定义好模型后,你需要将模型同步到数据库。这可以通过生成迁移文件并应用它们来实现:

yarn prisma migrate dev --name init

此命令将生成一个迁移文件,并将其应用到数据库。

  • --name 用来指定这次迁移的名称,在这个例子中是 init。迁移名称会被用来创建迁移文件夹的名称,这些文件夹位于 prisma/migrations/ 目录下。
  • 例如,如果使用 --name init,生成的迁移文件夹可能会被命名为 20240923030702_init,其中 20240923030702 是一个时间戳,init 是你指定的迁移名称。

执行完成之后,数据库中就会创建完成如上图所示的表。

生成的迁移文件夹

image <em>1</em>.png

数据库中创建的表结构

image <em>2</em>.png

注意:

在生产环境中,yarn prisma migrate dev 不适合直接执行。这个命令通常用于开发阶段,它会生成迁移文件并应用到数据库。在生产环境下,应该使用 yarn prisma migrate deploy 来应用已经生成的迁移文件,而不是使用 yarn prisma migrate devyarn prisma migrate devprisma/migrations 目录中创建迁移文件。yarn prisma migrate deploy会检测 prisma/migrations 目录中的迁移文件,并依次将这些迁移应用到生产数据库中,确保生产数据库结构与 Prisma Schema 文件一致。可以使用 yarn prisma migrate status 命令来查看当前数据库的迁移状态,确保迁移是否已经成功应用或需要手动处理。

这一步的目录是数据库表与 Prisma Schema 一致,因此我们也可以不用 migrate,而是手动建表,只需要确保 Prisma 的 schema.prisma 文件中的模型与手动创建的数据库表结构保持一致。

此外,如果你有一个现有的数据库,而不想手动编写 schema.prisma 文件,你可以使用 Prisma 的 db pull 命令,它会自动从数据库中导入表结构,并更新 schema.prisma 文件。

执行以下命令来同步数据库结构:

yarn prisma db pull

这会根据数据库中的表生成相应的 Prisma 模型,并将其写入到 schema.prisma 文件中。

生成 Prisma Client #

Prisma Client 是一个自动生成的、类型安全的数据库客户端,用于与数据库交互。生成客户端可以通过以下命令完成:

yarn prisma generate

yarn prisma generate 命令会根据 schema.prisma 文件中的定义自动生成一个类型安全的数据库客户端,通常位于 node_modules/.prisma/client 中。这个客户端提供了各种数据库操作的接口,用于执行查询、创建、更新和删除操作。每次你对 schema.prisma 文件进行修改(例如新增或修改模型),需要运行 yarn prisma generate 来重新生成 Prisma Client,以确保客户端代码与最新的数据库模型保持同步。

在 Next.js 中使用 Prisma Client #

通过 Prisma Client,开发者可以通过简单直观的 API 来执行 CRUD(创建、读取、更新、删除)操作,而不需要编写复杂的 SQL 语句。例如:

Create - 创建一个新的用户 #

const item = await prisma.user.create({
  data: {
    email: req.email,
    name: req.name,
  },
});
  • 说明emailname 是输入的用户数据。createdAt 使用的是默认值,updatedAt 会在记录创建或更新时自动更新。

Delete - 删除指定用户 #

const item = await prisma.user.delete({
  where: {
    id: parseInt(req.id),
  },
});
  • 说明:此操作会根据指定的 id 删除用户,并返回被删除用户的记录。

Update - 更新指定用户信息 #

const item = await prisma.user.update({
  where: {
    id: parseInt(req.id),
  },
  data: {
    name: req.name,
  },
});
  • 说明:此操作根据 id 查找用户并更新其 nameupdatedAt 字段将自动更新为当前时间。

Read - 查找用户 #

查找单个用户 #

const user = await prisma.user.findUnique({
  where: {
    id: 1,
  },
});
  • 说明findUnique 用于查找单条记录,可以根据 id 或其他唯一字段(如 email)。

查找多个用户 #

const items = await prisma.user.findMany();
  • 说明findMany 用于查找多条记录,支持多种查询条件,例如模糊查询、范围查询等。

常用查询方法: #

  • findUnique:查询单条记录。
  • findMany:查询多条记录。
  • create:创建一条新记录。
  • update:更新记录。
  • delete:删除记录。
  • upsert:更新或创建记录。
  • aggregate:执行聚合查询,如 countavgsum

设置 Prisma Client #

在 Next.js 中,Prisma Client 通常在服务器端代码中使用。首先,在项目中创建一个文件用于实例化 Prisma Client:

// lib/prisma.js
import { PrismaClient } from '@prisma/client';

const globalForPrisma = global;

const prisma = globalForPrisma.prisma || new PrismaClient();

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

export default prisma;

此文件确保在开发环境中只实例化一次 Prisma Client,以避免热重载导致的多次实例化问题。

在 API 路由中使用 Prisma #

我们把之前代码里的增、删、改、查放到 app/api/user/route.js 中,完整代码如下:

import prisma from "../../lib/prisma";

export async function POST(request) {
  const req = await request.json();

  const item = await prisma.user.create({
    data: {
      email: req.email,
      name: req.name,
    },
  });

  return new Response(
    JSON.stringify({
      id: item.id,
      email: item.email,
      name: item.name,
      createdAt: item.createdAt,
    }),
    {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }
  );
}

export async function DELETE(request) {
  const req = await request.json();

  const item = await prisma.user.delete({
    where: {
      id: parseInt(req.id),
    },
  });

  return new Response(
    JSON.stringify({
      id: item.id,
      message: `User with ID ${item.id} deleted successfully.`,
    }),
    {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }
  );
}

export async function PUT(request) {
  const req = await request.json();

  const item = await prisma.user.update({
    where: {
      id: parseInt(req.id),
    },
    data: {
      name: req.name,
    },
  });

  return new Response(
    JSON.stringify({
      id: item.id,
      email: item.email,
      name: item.name,
      updatedAt: item.updatedAt,
    }),
    {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }
  );
}

export async function GET() {
  const items = await prisma.user.findMany();

  return new Response(
    JSON.stringify(
      items.map((item) => ({
        id: item.id,
        email: item.email,
        name: item.name,
        createdAt: item.createdAt,
      }))
    ),
    {
      status: 200,
      headers: { "Content-Type": "application/json" },
    }
  );
}

然后,再写一个简单的页面 app/page.js

export default function Home() {
  // 创建用户
  const createUser = async () => {
    const res = await fetch("/api/user", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email: "example@dram.ai", name: "example" }),
    });

    if (!res.ok) {
      throw new Error(`HTTP error! status: ${res.status}`);
    }
    const data = await res.json();
    console.log("Response Data:", data);
  };

  // 删除用户
  const deleteUser = async () => {
    const res = await fetch(`/api/user`, {
      method: "DELETE",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ id: 1 }),
    });

    if (!res.ok) {
      throw new Error(`HTTP error! status: ${res.status}`);
    }
    const data = await res.json();
    console.log("Response Data:", data);
  };

  // 更新用户
  const updateUser = async () => {
    const res = await fetch("/api/user", {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ id: "1", name: "example2" }),
    });

    if (!res.ok) {
      throw new Error(`HTTP error! status: ${res.status}`);
    }
    const data = await res.json();
    console.log("Response Data:", data);
  };

  // 更新用户
  const getUser = async () => {
    const res = await fetch("/api/user", {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!res.ok) {
      throw new Error(`HTTP error! status: ${res.status}`);
    }
    const data = await res.json();
    console.log("Response Data:", data);
  };

  return (
    <div className="p-10 flex flex-col justify-start items-start">
      <button onClick={createUser}>Create User</button>
      <button onClick={updateUser}>Update User</button>
      <button onClick={deleteUser}>Delete User</button>
      <button onClick={getUser}>Get User</button>
    </div>
  );
}

最后分别点击对应的按钮,可以看到数据库增、删、改、查操作都被正确执行。

image <em>3</em>.png

image <em>4</em>.png

image <em>5</em>.png

image <em>6</em>.png

总结 #

通过将 Prisma 集成到 Next.js 中,可以简化数据库操作,使得数据管理更加直观高效。

Next.js实战教程 - 系列文章
Part 9: 当前文章