Prisma学习

5 阅读5分钟

1. Prisma 的核心结构

你只需要记住:

prisma.模型名.方法()

比如:

prisma.user.findMany()
prisma.word.create()
prisma.post.update()

如果你的模型是:

model Word {
  id      Int    @id @default(autoincrement())
  text    String
  meaning String
  level   String
}

那么生成的 API 就是:

prisma.word.findMany()
prisma.word.create()
prisma.word.update()
prisma.word.delete()

注意:模型 Word,调用时是小写 word


2. 查:findMany / findUnique / findFirst

查询全部

const words = await prisma.word.findMany()

按唯一字段查

const word = await prisma.word.findUnique({
  where: {
    id: 1,
  },
})

适合查:

id
email
slug

这类唯一字段。

查第一个符合条件的

const word = await prisma.word.findFirst({
  where: {
    level: 'G1',
  },
})

区别:

findUnique:只能用唯一字段
findFirst:普通条件也可以
findMany:返回数组

3. 增:create / createMany

创建一条

const word = await prisma.word.create({
  data: {
    text: 'apple',
    meaning: '苹果',
    level: 'G1',
  },
})

批量创建

await prisma.word.createMany({
  data: [
    { text: 'apple', meaning: '苹果', level: 'G1' },
    { text: 'banana', meaning: '香蕉', level: 'G1' },
  ],
})

跳过重复数据

前提是字段有 @unique

model Word {
  id      Int    @id @default(autoincrement())
  text    String @unique
  meaning String
  level   String
}
await prisma.word.createMany({
  data: [
    { text: 'apple', meaning: '苹果', level: 'G1' },
  ],
  skipDuplicates: true,
})

4. 改:update / updateMany / upsert

修改一条

await prisma.word.update({
  where: {
    id: 1,
  },
  data: {
    meaning: '苹果;苹果树',
  },
})

批量修改

await prisma.word.updateMany({
  where: {
    level: 'G1',
  },
  data: {
    level: 'Grade1',
  },
})

有就更新,没有就创建:upsert

这个很重要,适合导入词库。

await prisma.word.upsert({
  where: {
    text: 'apple',
  },
  update: {
    meaning: '苹果',
    level: 'G1',
  },
  create: {
    text: 'apple',
    meaning: '苹果',
    level: 'G1',
  },
})

5. 删:delete / deleteMany

删除一条

await prisma.word.delete({
  where: {
    id: 1,
  },
})

按条件批量删除

await prisma.word.deleteMany({
  where: {
    level: 'test',
  },
})

危险操作:

await prisma.word.deleteMany()

这会清空整张表。


6. 条件查询 where

等于

where: {
  level: 'G1'
}

不等于

where: {
  level: {
    not: 'G1',
  },
}

包含

where: {
  text: {
    contains: 'app',
  },
}

开头匹配

where: {
  text: {
    startsWith: 'a',
  },
}

结尾匹配

where: {
  text: {
    endsWith: 'e',
  },
}

大于 / 小于

where: {
  id: {
    gt: 10,
  },
}

常用:

gt   // 大于
gte  // 大于等于
lt   // 小于
lte  // 小于等于

多条件 AND

where: {
  AND: [
    { level: 'G1' },
    { text: { contains: 'a' } },
  ],
}

其实这样也默认是 AND:

where: {
  level: 'G1',
  text: {
    contains: 'a',
  },
}

OR

where: {
  OR: [
    { level: 'G1' },
    { level: 'G2' },
  ],
}

NOT

where: {
  NOT: {
    level: 'G1',
  },
}

7. 排序 orderBy

const words = await prisma.word.findMany({
  orderBy: {
    id: 'desc',
  },
})

多个排序:

const words = await prisma.word.findMany({
  orderBy: [
    { level: 'asc' },
    { id: 'desc' },
  ],
})

8. 分页 take / skip

第 1 页,每页 10 条

const page = 1
const pageSize = 10

const words = await prisma.word.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize,
})

第 2 页

const words = await prisma.word.findMany({
  skip: 10,
  take: 10,
})

这叫 offset 分页,适合后台管理。


9. 游标分页 cursor

适合 App 信息流、单词列表、无限滚动。

const words = await prisma.word.findMany({
  take: 10,
  cursor: {
    id: 100,
  },
  skip: 1,
  orderBy: {
    id: 'asc',
  },
})

意思是:

id = 100 后面开始,再取 10

10. select:只返回指定字段

const words = await prisma.word.findMany({
  select: {
    id: true,
    text: true,
    meaning: true,
  },
})

返回:

[
  { id: 1, text: 'apple', meaning: '苹果' }
]

不会返回 level

这是性能优化重点。


11. include:查询关联数据

假设你的表是:

model Word {
  id        Int        @id @default(autoincrement())
  text      String
  meaning   String
  level     String
  sentences Sentence[]
}

model Sentence {
  id      Int    @id @default(autoincrement())
  content String
  wordId  Int
  word    Word   @relation(fields: [wordId], references: [id])
}

查询单词时带上句子:

const words = await prisma.word.findMany({
  include: {
    sentences: true,
  },
})

返回结构类似:

[
  {
    id: 1,
    text: 'apple',
    meaning: '苹果',
    level: 'G1',
    sentences: [
      { id: 1, content: 'I eat an apple.' }
    ]
  }
]

12. include + select 组合

const words = await prisma.word.findMany({
  select: {
    id: true,
    text: true,
    sentences: {
      select: {
        id: true,
        content: true,
      },
    },
  },
})

这才是生产项目里最常用的写法。


13. 关系过滤 some / every / none

查询“有例句的单词”:

const words = await prisma.word.findMany({
  where: {
    sentences: {
      some: {},
    },
  },
})

查询“没有例句的单词”:

const words = await prisma.word.findMany({
  where: {
    sentences: {
      none: {},
    },
  },
})

查询“至少有一个例句包含 apple 的单词”:

const words = await prisma.word.findMany({
  where: {
    sentences: {
      some: {
        content: {
          contains: 'apple',
        },
      },
    },
  },
})

14. 嵌套创建 nested create

创建单词时,同时创建句子:

await prisma.word.create({
  data: {
    text: 'apple',
    meaning: '苹果',
    level: 'G1',
    sentences: {
      create: [
        { content: 'I eat an apple.' },
        { content: 'This is a red apple.' },
      ],
    },
  },
})

这非常适合你的英语学习 App。


15. connect:关联已有数据

假设已有一个 Word,现在创建句子并关联它:

await prisma.sentence.create({
  data: {
    content: 'I like apples.',
    word: {
      connect: {
        id: 1,
      },
    },
  },
})

等价于直接写:

await prisma.sentence.create({
  data: {
    content: 'I like apples.',
    wordId: 1,
  },
})

16. count:统计数量

const count = await prisma.word.count()

带条件:

const count = await prisma.word.count({
  where: {
    level: 'G1',
  },
})

分页接口经常这样写:

const [list, total] = await Promise.all([
  prisma.word.findMany({
    skip: 0,
    take: 10,
  }),
  prisma.word.count(),
])

17. 聚合 aggregate

const result = await prisma.word.aggregate({
  _count: {
    id: true,
  },
  _max: {
    id: true,
  },
  _min: {
    id: true,
  },
})

常用于统计。


18. 分组 groupBy

按等级统计单词数量:

const result = await prisma.word.groupBy({
  by: ['level'],
  _count: {
    id: true,
  },
})

返回类似:

[
  { level: 'G1', _count: { id: 300 } },
  { level: 'G2', _count: { id: 450 } }
]

19. 事务 $transaction

写法一:数组事务

await prisma.$transaction([
  prisma.word.create({
    data: {
      text: 'apple',
      meaning: '苹果',
      level: 'G1',
    },
  }),
  prisma.word.create({
    data: {
      text: 'banana',
      meaning: '香蕉',
      level: 'G1',
    },
  }),
])

意思是:

要么全部成功
要么全部失败

写法二:函数事务

await prisma.$transaction(async (tx) => {
  const word = await tx.word.create({
    data: {
      text: 'apple',
      meaning: '苹果',
      level: 'G1',
    },
  })

  await tx.sentence.create({
    data: {
      content: 'I eat an apple.',
      wordId: word.id,
    },
  })
})

适合前后操作有依赖关系的场景。


20. 原生 SQL:$queryRaw

Prisma 搞不定复杂 SQL 时用它。

const result = await prisma.$queryRaw`
  SELECT * FROM "Word" WHERE level = ${'G1'}
`

注意:不要拼字符串。

错误写法:

await prisma.$queryRawUnsafe(
  `SELECT * FROM "Word" WHERE level = '${level}'`
)

容易 SQL 注入。


21. Prisma Studio

可视化查看数据库:

npx prisma studio

打开后可以直接看表、改数据。


22. 常用 CLI 命令

npx prisma init

初始化 Prisma。

npx prisma generate

根据 schema.prisma 生成客户端。

npx prisma migrate dev --name init

开发环境创建迁移并同步数据库。

npx prisma migrate deploy

生产环境执行迁移。

npx prisma db push

直接把 schema 推到数据库,不生成 migration,适合原型开发。

npx prisma studio

打开可视化数据库管理界面。


23. schema.prisma 重点语法

主键

id Int @id @default(autoincrement())

唯一字段

email String @unique

默认值

createdAt DateTime @default(now())

更新时间

updatedAt DateTime @updatedAt

可选字段

avatar String?

枚举

enum WordLevel {
  G1
  G2
  G3
}
model Word {
  id    Int       @id @default(autoincrement())
  text  String
  level WordLevel
}

索引

model Word {
  id    Int    @id @default(autoincrement())
  text  String
  level String

  @@index([level])
}

联合唯一

model Word {
  id    Int    @id @default(autoincrement())
  text  String
  level String

  @@unique([text, level])
}

意思是:

同一个 level 里 text 不能重复

学习顺序

第一轮:create / findMany / update / delete
第二轮:where / orderBy / skip / take
第三轮:select / include
第四轮:一对多关系
第五轮:事务
第六轮:groupBy / aggregate
第七轮:raw SQL

你的目标不是“记住所有 API”,而是形成这个反射:

我要查数据 → findMany / findUnique
我要插数据 → create / createMany
我要改数据 → update / updateMany
我要有则改无则增 → upsert
我要关联查询 → include / select
我要安全处理多步写入 → transaction
我要复杂 SQL → queryRaw