让 Nextjs操作db更丝滑-Prisma 九点总结

229 阅读2分钟

一、背景

最近在找适合nextjs的ORM框架,然后发现了prisma,github star数达到了42.7k。该框架号称下一代orm框架,包含客户端,迁移模块和ui管理后台。因为之前是做后端的,自然的对比了java的mybatis-plus,我英语半吊子,但实际感觉这个下一代的含义不是多么的现代和先进,而是多么的年轻。资料比较少,吐血整理了几点技巧,遇到类似场景希望能有帮助。

二、九点总结

1、如何分页

分页有两种方式,一种是使用官方的,一种用插件。官方只能设置skip和take,类似sql分页的limit和offset参数;插件的封装了查询总页数的逻辑,用起来更加方便。

const results = await prisma.post.findMany({
    skip: 3,
    take: 4,
});

image-20250628101950434.png

或者使用cursor方式,这种性能好些

           const secondQueryResults = await prisma.post.findMany({
             take: 4,
             skip: 1, // Skip the cursor
             cursor: {
               id: myCursor,
             },
             where: {
               title: {
                 contains: 'Prisma' /* Optional filter */,
               },
             },
             orderBy: {
               id: 'asc',
             },
           })

           const lastPostInResults = secondQueryResults[3] // Remember: zero-based index! :)
           const myCursor = lastPostInResults.id // Example: 52
           

上面的例子中cursor就是每次查询比上次大的id。我感觉没必要,直接传入id查也一样的。

 //引用
 import { PrismaClient } from "@prisma/client";
 import { extension } from "prisma-paginate";

 //加入扩展
 const prisma = new PrismaClient();
 const xprisma = prisma.$extends(extension);

 //分页查询
 xprisma.model2
 .paginate({ limit: 10, page: 1, select: { id: true } })
 .then((result) => {
        console.log(result);
 });

上面page是页码,limit是每页页数,返回值有总页数,总数量和上一页下一页等。

2、 逻辑删除

一般公司要求不能直接物理删除数据,因此逻辑删除是必须滴,不然锅都不知道咋甩。逻辑删除也叫soft delete,该框架实现的比较繁琐,需要使用三方扩展,下面是一个推荐:github.com/olivierwilk… 使用很简单

  • 安装:npm install prisma-extension-soft-delete

  • 使用:经常使用delete作为标记删除字段,0表示未删除,1表是已删除

 const extendedClient = client.$extends(
       createSoftDeleteExtension({
       models: { //哪些表/model使用
             Comment: true,
             Post: true,
            },
            defaultConfig: { //通用配置
             field: "deleted",
             createValue: (deleted) => {
                if (deleted) return 1;
                  return 0;
                },
              },
             })
 );

这个框架会重写sql,查询自动添加delete条件。

3、自动填充
  • 自动填充时间 model可以使用如下注解:@now或者@updatedAt。@updatedAt 是 Prisma 模型中的一个特殊字段属性,用于自动更新记录的最后修改时间。每当记录被更新时,Prisma 会自动为该字段设置当前时间,无需手动操作。
model user {
   createdAt  DateTime   @default(now())
   updatedAt DateTime @updatedAt
}

自动填充其他字段需要自定义middlware,以下是示例, 操作记录表填充userid


prisma.$use(async (params, next) => {
     // Check incoming query type
     if (params.model == 'operation_log' && params.action == 'create') {
           params['user_id'] = getUserIdFromContext();
     }
     return next(params)
 })
4、字段映射

数据库定义的字段是下划线,代码要求驼峰,则可以添加map映射,如下:

model MsgAccount {
   id    BigInt  @id @default(autoincrement())
   msgId Int?    @map("msg_id")
   tag   String? @db.VarChar(50)

   @@index([msgId], map: "idx_msg_id")
   @@map("t_msg_account")
}

@map("msg_id") 将数据表的msg_id映射为msgId

@@map用于映射表名称。类似mybatisplus的@table注解

注意:prisma db pull同步操作会保留我们自定义的map注解

5、schema与db同步

为什么提这一点呢,个人开发的话各种迁移命令都可以,在公司有各种限制,不能随便改sql。因此常用的两个命令是

*   npx prisma db pull: 同步sql到schema

*   npx prisma migrate:修改model后重新生成客户端

每当数据表增加字段可以用 pull命令同步到model,如果model修改了,可以用migrate生成client,这样就不会影响到数据表

6、事务和回滚

事务几乎是业务中必须的操作,如转账等。prisma官方的事务操作如下

www.prisma.io/docs/orm/pr…

import { selectUserTitles, updateUserName } from '@prisma/client/sql'
    const [userList, updateUser] = await prisma.$transaction([
    prisma.$queryRawTyped(selectUserTitles()),
    prisma.$queryRawTyped(updateUserName(2)),
])

或者放在一起使用

const updateTeam = prisma.team.update({
              where: {
                id: 1,
              },
              data: {
                name: 'Aurora Adventures Ltd',
              },
            })

            const updateUsers = prisma.user.updateMany({
              where: {
                teams: {
                  some: {
                    id: 1,
                  },
                },
                name: {
                  equals: null,
                },
              },
              data: {
                name: 'Unknown User',
              },
            })

    await prisma.$transaction([updateUsers, updateTeam])

其他更复杂的操作可以见文档。感觉这块不如spring事务处理的更加精细,有各种传播机制和事务等,如果是复杂的大型业务还是建议使用java。

7、log日志

官方文档:www.prisma.io/docs/orm/pr…

            const prisma = new PrismaClient({
            log: [
            { emit: 'stdout', level: 'query' },
            { emit: 'stdout', level: 'error' },
            { emit: 'stdout', level: 'info' },
            { emit: 'stdout', level: 'warn' },
            ],
            });

目前还没有封装特别细致的第三方库,可能后面会有。

8、自定义sql

有时候逻辑复杂,需要自定义sql,这个时候可以使用rawquery www.prisma.io/docs/orm/pr…

 const email = "emelie@prisma.io";
 const result = await prisma.$queryRaw(Prisma.sql`SELECT * FROM User WHERE email = ${email}`);
9、字段加密

像passowrd,身份证号,银行卡这些需要加密存储,可以这样定义mddleware

import { PrismaClient } from '@prisma/client'
import bcrypt from 'bcrypt'

 const prisma = new PrismaClient().$extends({
     query: {
        user: {
          $allOperations({ operation, args, query }) {
             if (['create', 'update'].includes(operation) && args.data['password']) {
                 args.data['password'] = bcrypt.hashSync(args.data['password'], 10)
             }
             return query(args)
            }
          }
        }
  });

新建或者着update会自动更新密码

三、总结

使用下来的感受是能够是适配大部分的场景,类似中小企业或者个人开发,这个框架还支持sqlite这种嵌入式数据库,因此一般业务可以使用。从上面总结也能看出,这个框架不适合超大复杂业务,很多场景没有支持或者不够精细。

本人公众号大鱼七成饱,历史文章会在上面同步

image.png