Skip to content

Prisma 是一个现代化的数据库工具,通常用于与 JavaScript/TypeScript 项目集成。它能大幅简化数据库操作,特别是在构建全栈应用时。以下是 Prisma 的主要功能和优势:

  1. 主要功能
  • ORM:提供类型安全的数据库查询,同时支持复杂的关系查询。
  • Schema 管理:通过 Prisma Schema 定义数据库模型,可以自动生成数据库迁移。
  • 代码生成:自动生成类型化的数据库客户端,支持更快的开发和减少错误。
  • 多数据库支持:支持多种数据库,如 PostgreSQL、MySQL、SQLite、MongoDB 和 Microsoft SQL Server。
  1. 核心组件
  • Prisma Client:用于在应用中执行数据库查询,类型安全且支持自动补全。
  • Prisma Migrate:管理数据库模式的迁移工具。
  • Prisma Studio:一个直观的 Web 界面,用于浏览和操作数据库数据。
  1. 使用场景
  • 构建全栈应用(如使用 Next.js)。
  • 替代传统 ORM 工具(如 Sequelize 或 TypeORM)。
  • 在 TypeScript 项目中进行安全且高效的数据库操作。

创建项目

  1. 创建一个空文件夹
bash
npm init -y
npm install prisma typescript tsx @types/node --save-dev
  1. 初始化TypeScript

    bash
    npx tsc --init npx tsc -- 初始化

    这条命令会完成两个操作:

    • 创建一个名为 prisma 的新目录,其中包含一个名为 schema.prisma 的文件,该文件包含 Prisma 架构以及数据库连接变量和架构模型
    • 在项目的根目录中创建 .env 文件,该文件用于定义环境变量(例如数据库连接)
  2. 初始化prisma

    bash
    npx prisma init

连接数据库

首先在schema.prisma文件中可以看到

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

provider根据数据库类型进行更改,例:mysql

url则是数据库连接地址,可以在.env中进行修改

image-20241125163209658

其他参数:

image-20241125164246802

测试是否可以连接成功

bash
npx prisma db pull

该命令会尝试连接到数据库,并拉取现有数据库的结构。

如果数据库是空的会返回:

bash
Error: 
P4001 The introspected database was empty:

不是空的则会返回:

bash
 Your database schema was successfully fetched.

两种情况都能说明数据库连接成功。

创建数据库模型

ts
//schema.prisma
model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String   @db.VarChar(255)
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  user   User    @relation(fields: [userId], references: [id])
  userId Int     @unique
}

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  posts   Post[]
  profile Profile?
}

执行prisma migrate CLI 命令

bash
npx prisma migrate dev --name init

此命令执行两项操作:

  1. 它为此次迁移创建一个新的 SQL 迁移文件。
  2. 它针对数据库运行 SQL 迁移文件。

image-20241125170039014

image-20241125170142736

增删改查操作

在根目录新建index.ts文件,在该目录实现增删改查操作。

新增

ts
//index.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
    await prisma.user.create({
        data: {
            name: 'Alice',
            email: 'alice@prisma.io',
            posts: {
                create: { title: 'Hello World' },
            },
            profile: {
                create: { bio: 'I like turtles' },
            },
        },
    })

    const allUsers = await prisma.user.findMany({
        include: {
            posts: true,
            profile: true,
        },
    })
    console.dir(allUsers, { depth: null })
}
main()
    .then(async () => {
        await prisma.$disconnect()
    })
    .catch(async (e) => {
        console.error(e)
        await prisma.$disconnect()
        process.exit(1)
    })

使用npx tsx index.ts命令运行

修改

新增edit函数,结尾改为运行edit()函数,其它不做修改

ts
//index.ts
//修改
async function edit() {
    const user = await prisma.user.update({ where: { id: 1 }, data: { name: '张三' } })
    console.log('====================================');
    console.log(user);
    console.log('====================================');
}

删除

执行删除操作前,在新增一条数据。

ts
// 新增
async function add() {
    await prisma.user.create({
        data: {
            name: '李四',
            email: 'lisi@prisma.io',
            posts: {
                create: { title: 'Hello World' },
            },
            profile: {
                create: { bio: 'I like turtles' },
            },
        },
    })

    const allUsers = await prisma.user.findMany({
        //include 选项传递给 findMany,它告诉 Prisma Client 在返回的 User 对象上包含 posts 和 profile 关系
        include: {
            posts: true,
            profile: true,
        },
    })
    console.dir(allUsers, { depth: null })
}

schema.prisma文件中设置级联删除操作

ts
model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String   @db.VarChar(255)
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id],onDelete: Cascade)// 配置级联删除
  authorId  Int
}

model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  user   User    @relation(fields: [userId], references: [id],onDelete: Cascade)// 配置级联删除
  userId Int     @unique
}

onDelete: Cascade 表示当用户被删除时,关联的所有 Post、Profile 记录也会被删除。

重新应用迁移

bash
npx prisma migrate dev --name add_cascade_delete

除了 onDelete: Cascade,Prisma 支持以下几种行为:

选项描述
Cascade删除父记录时,自动删除所有相关子记录。
SetNull删除父记录时,将子记录中的外键字段设置为 NULL
Restrict (默认)如果有子记录引用父记录,则禁止删除父记录,避免违反外键约束。
NoAction不做任何操作,由数据库决定行为(类似 Restrict,但更灵活)。
ts
//index.ts
//删除
async function removeUserById(id: number) {
    await prisma.user.delete({ where: { id } })
}
removeUserById(2)
    .then(async () => {
        await prisma.$disconnect()
    })
    .catch(async (e) => {
        console.error(e)
        await prisma.$disconnect()
        process.exit(1)
    })

查询

查询一下是否已完成删除操作

ts
// 查询
async function findAllUser() {
    const allUsers = await prisma.user.findMany({
        include: {
            profile: true,
            posts: true
        }
    })
    console.log(allUsers);
}

删除成功,输出结果如下:

bash
[
  {
    id: 1,
    email: 'alice@prisma.io',
    name: '张三',
    profile: { id: 1, bio: 'I like turtles', userId: 1 },
    posts: [ [Object] ]
  }
]

一对多

ts
//一个用户多篇文章
model User {
  id             String @id @default(cuid())
  email          String @unique()
  hashedPassword String
  posts          Post[]
}

model Post {
  id        String   @id @default(cuid())
  title     String
  slug      String   @unique
  content   String
  //?表示该字段是可选的
  published Boolean? @default(false)
  updatedAt DateTime @updatedAt
  createdAt DateTime @default(now())
  author     User    @relation(fields: [authorId], references: [id])
  authorId    String
}

多对多

ts
//多个用户多篇文章
model User {
  id             String @id @default(cuid())
  email          String @unique()
  hashedPassword String
  posts          Post[]
}

model Post {
  id        String   @id @default(cuid())
  title     String
  slug      String   @unique
  content   String
  //?表示该字段是可选的
  published Boolean? @default(false)
  updatedAt DateTime @updatedAt
  createdAt DateTime @default(now())
  author     User[]
}

一对一

ts
//一个用户一篇文章
model User {
  id             String @id @default(cuid())
  email          String @unique()
  hashedPassword String
  posts          Post?
}

model Post {
  id        String   @id @default(cuid())
  title     String
  slug      String   @unique
  content   String
  //?表示该字段是可选的
  published Boolean? @default(false)
  updatedAt DateTime @updatedAt
  createdAt DateTime @default(now())
  author     User    @relation(fields: [authorId], references: [id])
  authorId    String  @unique
}

一图搞懂Prisma在应用中所处位置

image-20241126152037553

在项目中集成Prisma

  1. 下载依赖

    bash
    npm i prisma --save-dev
  2. 初始化Prisma

    bash
    npx prisma init --datasource-provider sqlite
  3. schema.prisma文件中添加模型

    ts
    model Post {
      id        String   @id @default(cuid())
      title     String
      content   String
      //?表示该字段是可选的
      published Boolean? @default(false)
      updatedAt DateTime @updatedAt
      createdAt DateTime @default(now())
    }
  4. 运行npx prisma db push

  • 同步 Prisma 数据模型

    • schema.prisma 中定义的模型直接推送到数据库。

    • 如果数据库中不存在表,会创建表。

    • 如果表已经存在,会根据模型更新表结构(但不会删除已有数据)。

  • 适合场景

    • 开发阶段快速更新数据库结构。

    • 临时修改数据库结构。

  • 不适合生产环境

    • 它不会生成迁移文件,因此不适合生产环境的数据库管理。

image-20241126170636488

  1. 运行npx prisma studio命令可以打开一个数据库视图,新增一条数据。

  2. 新建/lib/db.ts文件

    ts
    import { PrismaClient } from '@prisma/client'
    
    const prismaClientSingleton = () => {
        return new PrismaClient()
    }
    
    declare const globalThis: {
        prismaGlobal: ReturnType<typeof prismaClientSingleton>;
    } & typeof global;
    
    const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
    
    export default prisma
    
    if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
  3. 查询post所有数据

    tsx
    import ThemeToggleButton from "@/components/ThemeToggleButton";
    import prisma from "@/lib/db";
    import Link from "next/link";
    export default async function Home() {
      const posts = await prisma.post.findMany();
      const postsCount = await prisma.post.count();
      return (
        <div className="w-screen h-screen flex flex-col items-center  bg-white dark:bg-black sm:py-5 py-2">
          <ThemeToggleButton />
          <h1 className="text-blue-400">All POST({postsCount})</h1>
          {posts.map((item, index) => {
            return (
              <Link href={`/posts/${item.id}`} key={index}>
                <p className="text-yellow-400">{item.title}</p>
              </Link>
            );
          })}
        </div>
      );
    }
  4. 查询详情

    tsx
    import prisma from "@/lib/db";
    
    const PagesDetail = async ({ params }: { params: { id: string } }) => {
      const detail = await prisma.post.findUnique({ where: { id: params.id } });
      return <div className="">{detail?.content}</div>;
    };
    
    export default PagesDetail;
  5. 在数据库文件中添加 slug字段 添加@unique 确保该字段的值是唯一的,否则会报错。

    ts
    model Post {
      id        String   @id @default(cuid())
      title     String
      slug      String   @unique
      content   String
      //?表示该字段是可选的
      published Boolean? @default(false)
      updatedAt DateTime @updatedAt
      createdAt DateTime @default(now())
    }

    运行npx prisma db push命令,注意:此命令会导致数据会丢失

    image-20241127152858518

  6. 详情页面根据slug查询详情

    tsx
    import prisma from "@/lib/db";
    
    const PagesDetail = async ({ params }: { params: { slug: string } }) => {
      const detail = await prisma.post.findUnique({
        where: { slug: decodeURIComponent(params.slug) },
      });
      return (
        <div className="flex flex-col items-center p-2 sm:p-5">
          <div className="text-2xl font-bold">{detail?.title}</div>
          <div className="text-sm p-5">{detail?.content}</div>
        </div>
      );
    };
    
    export default PagesDetail;
  7. 其他一些操作

    tsx
     const posts = await prisma.post.findMany({
         //过滤出已发布的文章
        where: { published: true },
     	//按照创建时间降序    
        orderBy: {
          createdAt: "desc",
        },
         //要返回的字段
        select: {
          id: true,
          slug: true,
          title: true,
        },
        take: 2, // 获取前 2 条记录
      	skip: 0, // 从第 0 条开始,不跳过任何记录
      });

    分页:https://www.prisma.io/docs/orm/prisma-client/queries/pagination

  8. 添加文章

    tsx
    import { createPost } from "../action/action";
    const addPost = () => {
      return (
        <div className="flex flex-col items-center pt-10">
          <span className="text-2xl font-bold">添加</span>
          <form action={createPost} className="flex flex-col gap-5 py-5">
            <input
              type="text"
              name="title"
              placeholder="Title"
              className="px-2 py-1 rounded-sm border"
            />
            <textarea
              name="content"
              rows={5}
              placeholder="Conent"
              className="px-2 py-1 rounded-sm border"
            />
            <button
              type="submit"
              className="bg-blue-500 py-2 text-white rounded-sm"
            >
              提交
            </button>
          </form>
        </div>
      );
    };
    
    export default addPost;
  9. 编辑文章

    tsx
  10. 删除文章

    tsx
    // remmoveBtn
    "use client";
    import { IoIosCloseCircle } from "react-icons/io";
    import { deletePost } from "@/app/action/action";
    const RemoveBtn = ({ id }: { id: string }) => {
      const handleRemove = async () => {
        await deletePost(id);
      };
    
      return (
        <IoIosCloseCircle
          onClick={handleRemove}
          className="text-[#C20E4D] cursor-pointer"
        />
      );
    };
    
    export default RemoveBtn;
  11. action文件

    ts
    //action/action.ts
    "use server"
    import prisma from "@/lib/db"
    import { revalidatePath } from "next/cache"
    import { redirect } from "next/navigation"
    
    export async function createPost(formData: FormData) {
        await prisma.post.create({
            data: {
                title: formData.get("title") as string,
                content: formData.get("content") as string,
                slug: formData.get("title") as string
            }
        })
        revalidatePath("/")
        redirect('/')
    }
    export async function editPost(formData: FormData, id: string) {
        await prisma.post.update({
            where: { id },
            data: {
                title: formData.get("title") as string,
                content: formData.get("content") as string,
                slug: formData.get("title") as string
            }
        })
        revalidatePath("/")
        redirect('/')
    
    }
    export async function deletePost(id: string) {
        await prisma.post.delete({ where: { id } })
        revalidatePath("/")
    }
  12. 一对多关系

    ts
    model User {
      id             String @id @default(cuid())
      email          String @unique()
      hashedPassword String
      posts          Post[]
    }
    
    model Post {
      id        String   @id @default(cuid())
      title     String
      slug      String   @unique
      content   String
      //?表示该字段是可选的
      published Boolean? @default(false)
      updatedAt DateTime @updatedAt
      createdAt DateTime @default(now())
      author     User    @relation(fields: [authorId], references: [id])
      authorId    String
    }

    更新数据库npx prisma db push

  13. 添加一条试试

    ts
    export async function createPost(formData: FormData) {
        await prisma.post.create({
            data: {
                title: formData.get("title") as string,
                content: formData.get("content") as string,
                slug: formData.get("title") as string,
                author: {
                    connect: {
                        email: "1404340013@qq.com"
                    }
                }
            }
        })
        revalidatePath("/")
        redirect('/')
    }
  14. 初始化数据

    ts
    // /prisma/seed.ts
    import { Prisma, PrismaClient } from '@prisma/client'
    const prisma = new PrismaClient()
    const initialPosts: Prisma.PostCreateInput[] = [
        {
            title: "post 1",
            slug: "post-1",
            content: "个人皇家公馆热加工戈培尔工卡让娃",
            author: {
                connectOrCreate: {
                    where: {
                        email: "18839362311@163.com"
                    },
                    create: {
                        email: "18839362311@163.com",
                        hashedPassword: "flefhjkhfjkhjjk"
                    }
                }
            }
        }
    ]
    
    async function main() {
        console.log("start seeding...");
        for (const post of initialPosts) {
            const newPost = await prisma.post.create({
                data: post
            })
            console.log(`"new postid"${newPost.id}`);
    
        }
        console.log("seeding finish");
    }
    main()
        .then(async () => {
            await prisma.$disconnect()
        })
        .catch(async (e) => {
            console.error(e)
            await prisma.$disconnect()
            process.exit(1)
        })
  15. package.json中添加命令行

    js
      "prisma": {
        "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
      },

    下载依赖npm i ts-node -D

    执行npx prisma db seed命令

  16. 捕获错误信息

    ts
    export async function createPost(formData: FormData) {
        try {
            await prisma.post.create({
                data: {
                    title: formData.get("title") as string,
                    content: formData.get("content") as string,
                    slug: formData.get("title") as string,
                    author: {
                        connect: {
                            email: "18839362311@163.com"
                        }
                    }
                }
            })
        } catch (error) {
            if (error instanceof Prisma.PrismaClientKnownRequestError)
                // console.log(error);
    
                switch (error.code) {
                    case 'P2002':
                        console.log("Unique constraint failed on the {constraint}");
                        break;
                    default:
                        break;
                }
            console.log("出错啦");
        }
    
        revalidatePath("/")
        redirect('/')
    }

    其它错误码文档

    https://www.prisma.io/docs/orm/reference/error-reference#p1012

  17. 数据库迁移npx prisma migrate dev

上次更新:

知识是财富,分享是快乐!