有了 Prisma,就别用 TypeORM 了

要说2024 年 Node.js 的 ORM 框架应该选择哪个?毫无疑问选 Prisma。至于为何,请听我细细道来。

本文面向的对象是饱受 TypeORM 折磨的资深用户(说的便是我自己)。只对这两个 ORM 框架从开发体验上进行对比,你也可以到 这里 查看 Prisma 官方对这两个 ORM 框架的对比。

整体对比

更新频率&下载量

TypeORM 距离上次更新已经几近半年了(下图来源 24 年 1 月 1 日,没想到年初竟然还复活的),

Untitled

从下载量以及 star 数来看,如今 Prisma 已经超过 TypeORM,这很大一部分的功劳归功于像 Next.js、Nuxt.js 这样的全栈框架。

Untitled

上图来源 https://npmtrends.com/prisma-vs-typeorm

而在 Nest.js 的 Discord 社区 讨论之中,Prisma 也成为诸多 Nest.js 开发者首选的 ORM 框架,因为它有着更好的开发体验。

在大势所趋之下相信你内心已经有一份属于自己的答案。

文档&生态

从文档的细致程度上 Prisma 比 TypeORM 要清晰详尽。在 Get started 花个数十分钟了解 Prisma 基本使用,到 playground.prisma.io 中在线尝试,到 learn 查看官方所提供的免费教程。

此外 Prisma 不仅仅只支持 js/ts 生态,还支持其他语言。丰富的生态下,加之 Prisma 开发团队的背后是由商业公司维护,无需担心担心夭折同时还能事半功倍。

Untitled

开发体验对比

在从开发体验上对比之前,我想先说说 TypeORM 都有哪些坑(不足)。

findOne(undefined) 所查询到的却是第一条记录

首先 TypeORM 有个天坑,你可以在 这个 Issue 中查看详情或查看 这篇文章 是如何破解使用 TypeORM 的 Node.js 应用。

当你使用 userRepository.findOne({ where: { id: null } }) 时,从开发者的预期来看所返回的结果应该为 null 才对,但结果却是大跌眼镜,结果所返回的是 user 表中的第一个数据记录!

你可能会说,这不是 bug 吗?为何官方还不修。事实上确实是 bug,而事实上官方到目前也还没修复该 bug。再结合上文提到的更新频率,哦,那没事了。

目前解决方法则是用 createQueryBuilder().where({ id }).getOne() 平替上一条语句或者确保查询参数不为 undefined。但从此而言也可以看的出,TypeORM 在现今或许并不是一个很好的选择。

synchronize: true 导致数据丢失

synchronize 表示数据库的结构是否和代码保持同步,官方提及到请不要在生产环境中使用,但在开发阶段这也并不是一个很好的做法。举个例子,有这么一个实体

@Entity()
export class User {@PrimaryGeneratedColumn()id: number@Column()name: string
}

当你将 name 更改为 title 时,会发现原有的 name 下的数据全都丢失了!

Untitled

因为 TypeORM 针对上述操作的 sql 语句是这样的

ALTER TABLE `user` CHANGE `name` `title` varchar(255) NOT NULL
ALTER TABLE `user` DROP COLUMN `title`
ALTER TABLE `user` ADD `title` varchar(255) NOT NULL

也就是说,当你在开发环境中,修改某个字段(包括名字,属性)时,该字段原有的数据便会清空。

因此针对数据库更新的操作最正确的做法是使用迁移(migrate)。

接入成本

在 Nest 项目中,Prisma 的接入成本远比 TypeORM 来的容易许多。

相信你一定有在 xxx.module.ts 中通过 TypeOrmModule.forFeature([xxxEntity]) 的经历。就像下面代码这样:

@Module({imports: [TypeOrmModule.forFeature([UserEntity])],controllers: [UserController],providers: [UserService],exports: [TypeOrmModule, UserService],
})
export class xxxModule {}

对于初学者而言,很大程度上会忘记 导入这段语句 就会出现这样的报错


Potential solutions:- Is DeptModule a valid NestJS module?- If "UserEntityRepository" is a provider, is it part of the current DeptModule?- If "UserEntityRepository" is exported from a separate @Module, is that module imported within DeptModule?@Module({imports: [ /* the Module containing "UserEntityRepository" */ ]})Error: Nest can't resolve dependencies of the userService (?). Please make sure that the argument "UserEntityRepository" at index [0] is available in the UserModule context.

此外这还不是最繁琐的,你还需要再各个 service 中,通过下面的代码来注入 userRepository

@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>

实体一多,要注入的 Repository 也就更多,无疑不是对开发者心智负担的加深。

再来看看 Prisma 是怎么导入的,你可以使用 nestjs-prisma 或者按照官方文档中创建 PrismaService。

然后在 service 上,注入 PrismaService 后,就可以通过 this.prisma[model] 来调用模型(实体) ,就像这样

import { Injectable } from '@nestjs/common';
import { PrismaService } from 'nestjs-prisma';@Injectable()
export class AppService {constructor(private prisma: PrismaService) {}users() {return this.prisma.user.findMany();}user(userId: string) {return this.prisma.user.findUnique({where: { id: userId },});}
}

哪怕创建其他新的实体,只需要重新生成 PrismaClient,都无需再导入额外服务。

更好的类型安全

Prisma 的贡献者中有 ts-toolbelt 的作者,正因此 Prisma 的类型推导十分强大,能够自动生成几乎所有的类型。

而反观 TypeORM 虽说使用 Typescript 所编写,但它的类型推导真是一言难尽。我举几个例子:

在 TypeORM 中,你需要 select 选择某个实体的几个字段,你可以这么写

Untitled

你会发现 post 对象的类型提示依旧还是 postEntity,没有任何变化。但从开发者的体验角度而言,**既然我选择查询 id 和 title 两个字段,那么你所返回的 post 类型应该也只有 id 与 title 才更符合预期。**而后续代码中由于允许 post 有 body 属性提示,那么 post.body 为 null 这样不必要的结果。

再来看看 Prisma,你就会发现 post 对象的类型提示信息才符合开发者的预期。像这样的细节在 Prisma 有非常多。

Untitled

这还不是最关键的,当 TypeORM 通过需要使用 createQueryBuilder 方法来构造 sql 语句才能够满足开发者所要查询的预期,而当你使用了该方法,你就会发现你所编写的代码与 js 无疑,我贴几张图给大伙看看。

Untitled

Untitled

Untitled

这无疑会诱发一些潜在 bug,我就多次因为要 select 某表中的某个字段,而因为拼写错误导致查询失败。

创建实体

在 TypeORM 中,假设你要创建一个 User 实体,你需要这么做

const newUser = new User()
newUser.name = 'kuizuo'
newUser.email = 'hi@kuizuo.cn'
const user = userRepository.save(newUser)

当然你可以对 User 实体中做点手脚,像下面这样加一个构造函数

@Entity({ name: 'user' })
export class UserEntity  {@PrimaryGeneratedColumn()id: number@Column({ unique: true })username: string@Column()email: stringconstructor(partial?: Partial<UserEntity>) {Object.assign(this, partial)}
}
const newUser = new User({name: 'kuizuo',email: 'hi@kuizuo.cn',
})
const user = userRepository.save(newUser)

于是你就可以传递一个 js 对象到 User 实体,而不是 newUser.xxx = xxx 像 Java 版的写法。

而要是涉及到多个关联的数据,往往需要先查询到关联数据,然后再像上面这样赋值+保存。这里就不展开了,使用过 TypeORM 的应该深有体会。

而在 Prisma 中,绝大多数的操作你都只需要一条代码语句外加一个对象结构,像上述 TypeORM 的操作对应 Prisma 的代码语句如下

const user = await prisma.user.create({data: {name: 'kuizuo',email: 'hi@kuizuo.cn',},
})

根据条件来创建还是更新

在数据库中操作经常需要判断数据库中是否有某条记录,以此来决定是更改该记录还是创建新的一条记录,而在 Prisma 中,完全可以使用 upsert,就像下面这样

const user = await prisma.user.upsert({where: { id: 1 },update: { email: 'example@prisma.io' },create: { email: 'example@prisma.io' },
})

聚合函数

在 TypeORM 中,假设你需要使用聚合函数来查询的话,通常会这么写

const raw = await this.userRepository.createQueryBuilder('user').select('SUM(user.id)', 'sum').getRawOne()const sum = raw.sum

如果只是像上面这样,单纯查询 sum,那么 raw 的值是 { sum: 1 } , 但最要命的就是 select 配合 getRawOne 还要额外查询 user 实体的属性,所得到的结果就像这样

const raw = await this.userRepository.createQueryBuilder('user').select('SUM(user.id)', 'sum').addSelect('user').where('user.id = :id', { id: 1 }).getRawOne()
{user_id: 1,user_name: 'kuizuo',user_email: 'hi@kuizuo.cn',sum: '1'
}

所有 user 的属性都会带有 user_ 前缀,这看上去有点不是那么合理,但如果考虑要联表查询的情况下,就会存在相同名称的字段,通过添加表名(别名)前缀就可以避免这种情况,这样来看貌似又有点合理了。

但还是回到熟悉的类型安全,这里的所返回的 raw 对象是个 any 类型,一样不会有任何提示。

而在 Prisma 中,提供了 专门用于聚合的方法 aggregate,可以特别轻松的实现聚合函数查询。

const minMaxAge = await prisma.user.aggregate({_count: {_all: true,},_max: {profileViews: true,},_min: {profileViews: true,},
})
{_count: { _all: 29 },_max: { profileViews: 90 },_min: { profileViews: 0 }
}

看到这里,你若是长期使用 TypeORM 的用户必定会感同身受如此糟糕的体验。那种开发体验真的是无法用言语来形容的。

Prisma 生态

分页

在 Prisma 你要实现分页,只需要在 prismaClient 继承 prisma-extension-pagination 这个库。就可像下面这样,便可在 model 中使用paginate方法来实现分页,如下代码。

import { PrismaClient } from '@prisma/client'
import { pagination } from 'prisma-extension-pagination'const prisma = new PrismaClient().$extends(pagination())
const [users, meta] = prisma.user.paginate().withPages({limit: 10,page: 2,includePageCount: true,});// meta contains the following
{currentPage: 2,isFirstPage: false,isLastPage: false,previousPage: 1,nextPage: 3,pageCount: 10, // the number of pages is calculatedtotalCount: 100, // the total number of results is calculated
}

支持页数(page)或光标(cursor)。

::: 两种分页的使用场景

按页查询通常

光标查询 则用于流式查看,例如无限下拉滚动

:::

而在 TypeORM 你通常需要自己封装一个 paginate方法,就如下面代码所示(以下写法借用 nestjs-typeorm-paginate)

async function paginate<T>(queryBuilder: SelectQueryBuilder<T>,options: IPaginationOptions,
): Promise<Pagination<T>> {const { page, limit } = optionsqueryBuilder.take(limit).skip((page - 1) * limit)const [items, total] = await queryBuilder.getManyAndCount()return createPaginationObject<T>({items,totalItems: total,currentPage: page,limit,})
}// example
const queryBuilder = userRepository.createQueryBuilder('user')
const { items, meta } = paginate(queryBuilder, { page, limit })

当然也可以自定义userRepository,为其添加 paginate 方法,支持链式调用。但这无疑增添了开发成本。

根据 Schema 自动生成数据验证

得益于 Prisma 强大的数据建模 dsl,通过 generators 生成我们所需要的内容(文档,类型),比如可以使用 zod-prisma-types 根据 Schema 生成 zod 验证器**。**

举个例子,可以为 schema.prisma 添加一条 generator,长下面这样

generator client {provider = "prisma-client-js"output   = "./client"
}generator zod {provider                         = "zod-prisma-types"output                           = "./zod"createModelTypes                 = true// ...rest of config
}datasource db {provider = "postgresql"url      = env("DATABASE_URL")
}model User {id         String      @id @default(uuid())email      String      @uniquename       String?
}

执行构建命令后,这将会自动生成 zod/index.ts 文件,将包含 UserSchema 信息,其中片段代码如下

export const UserSchema = z.object({id: z.string().uuid(),email: z.string(),name: z.string().nullable(),
})export type User = z.infer<typeof UserSchema>

再通过 createZodDto,将 zod 验证器转化为 dto 类,就像下面这样

Untitled

当然你可能并不想在 nestjs 项目中使用 zod,而是希望使用传统的 class-validator 来编写 dto。可以使用社区提供的 prisma-class-generator 根据已有 model 生成 dto。


合理来说,Prisma 并不是一个传统的 ORM,它的工作原理并不是将表映射到编程语言中的模型类,为处理关系数据库提供了一种面向对象的方式。而是在 Prisma Schema 中定义模型。在应用程序代码中,您可以使用 Prisma Client 以类型安全的方式读取和写入数据库中的数据,而无需管理复杂模型实例的开销。

总而言之,你若想要更好的类型,简洁的实体声明语法,况且带有可视化桌面端应用,以及更好的生态完备,那么你就应该选 Prisma。

总结

在写这篇文章时,我也是彻底的将 Nestjs 项目中由 TypeORM 迁移到 Prisma ,这期间给我最大的变化就是在极少的代码量却又能实现强大的功能。许多涉及多表的 CRUD操作可以通过一条简洁的表达式来完成,而在使用 TypeORM 时,常常需要编写繁琐臃肿的 queryBuilder。

TypeORM 有种被 nestjs 深度绑定的模样,一提到 TypeORM,想必第一印象就是 Nestjs 中所用到的 ORM 框架。然而,Prisma 却不同,是一个全能通用的选择,可以在任何的 js/ts 框架中使用。

从开发体验的角度不接受任何选择 TypeORM 的反驳,有了更优优秀的选择,便不愿意也不可能在回去了。如果你还未尝试过 Prisma,我强烈建议你亲身体验一番。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/237079.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装nvidia driver出现 the cc vision check falied

这里提示说的需要gcc12,但是我只有gcc11,所以就报错了&#xff0c;说一说我自己的解决方法&#xff1a; 安装gcc12和g12,再切换版本为gcc12 安装gcc12: sudo apt install gcc-12安装g12: sudo apt -y install g-12切换版本&#xff1a;参考博客

R语言【paleobioDB】——pbdb_map():根据化石记录绘制地图

Package paleobioDB version 0.7.0 paleobioDB 包在2020年已经停止更新&#xff0c;该包依赖PBDB v1 API。 可以选择在Index of /src/contrib/Archive/paleobioDB (r-project.org)下载安装包后&#xff0c;执行本地安装。 Usage pbdb_map (data, col.int"white" ,p…

如何使用PR制作抖音视频?抖音短视频创作素材剪辑模板PR项目工程文件

如何使用PR软件制作抖音视频作品&#xff1f;Premiere Pro 抖音短视频创作素材剪辑模板PR项目工程文件。 3种分辨率&#xff1a;10801920、10801350、10801080。 来自PR模板网&#xff1a;https://prmuban.com/37058.html

用React给XXL-JOB开发一个新皮肤(二):目录规划和路由初始化

目录 一. 简述二. 目录规划三. Vite 配置 3.1. 配置路径别名3.2. 配置 less 四. 页面 4.1. 入口文件4.2. 骨架文件4.3. 普通页面 五. 路由配置六. 预览启动 一. 简述 上一篇文章我们介绍了项目初始化&#xff0c;此篇文章我们会先介绍下当前项目的目录规划&#xff0c;接着对…

Python 中的字符串分割函数 split() 详解

更多Python学习内容&#xff1a;ipengtao.com 在 Python 编程中&#xff0c;处理字符串是一项常见的任务。字符串分割是其中的一个常见操作&#xff0c;而 Python 提供了强大的 split() 函数&#xff0c;用于将字符串拆分成多个部分。本文将详细介绍 split() 函数的用法、参数和…

Openstack组件glance对接swift

2、glance对接swift &#xff08;1&#xff09;可直接在数据库中查看镜像存放的位置、状态、id等信息 &#xff08;2&#xff09;修改glance-api的配置文件&#xff0c;实现对接swift存储&#xff08;配置文件在/etc/glance/glance-api.conf&#xff0c;建议先拷贝一份&#x…

黑马python就业课

文章目录 初级中级高级初级课程分享 初级 中级 高级 初级课程分享 链接&#xff1a;https://pan.baidu.com/s/1aiJHaThezv_mSI1rnV3d7g 提取码&#xff1a;xdpc

嵌套的CMake

hehedalinux:~/Linux/multi-v1$ tree . ├── calc │ ├── add.cpp │ ├── CMakeLists.txt │ ├── div.cpp │ ├── mult.cpp │ └── sub.cpp ├── CMakeLists.txt ├── include │ ├── calc.h │ └── sort.h ├── sort │ ├── …

Mnajora 使用deb包安装软件

说明 Mnajora 安装deb软件包主要有两种方式 可以使用dpkg 直接安装也可是使用debtap将deb软件包转换成 使用dpkg sudo pacman -S dpkg #安装dpkgsudo dpkg -i ###.deb #使用dpkg安装deb软件包和在ubuntu上是一样的 安装成功 使用debtap debtap是一个用于将.deb包转换为A…

im6ull学习总结(三-3)freetype

1、Freetype简介 FreeType是一个开源的字体渲染引擎&#xff0c;主要用于将字体文件转换为位图或矢量图形&#xff0c;并在屏幕上渲染出高质量的字体。它提供了一组API&#xff0c;使开发者能够在自己的应用程序中使用和呈现字体。 FreeType最初是作为一个独立项目开发的&…

07-Tomcat运行Jenkins并实现链路追踪

4.3.1&#xff1a;部署skywalking java agent ~# apt install openjdk-11-jdk -y ~# cd /apps/ ~# wget https://archive.apache.org/dist/skywalking/java-agent/9.0.0/apache-skywalking-java-agent-9.0.0.tgz ~# tar xf apache-skywalking-java-agent-9.0.0.tgz ~# vim /ap…

Git的安装

1、下载 官网地址&#xff1a; https://git-scm.com/或https://github.com/git-for-windows/git/releases 百度网盘链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/13_asGO-XQb5KWWH_V7rq6g?pwd0630 2、安装 ①查看GNU协议&#xff0c;可以直接点击下一步。 ②…

橘子学Spring01之spring的那些工厂和门面使用

一、Spring的工厂体系 我们先来说一下spring的工厂体系(也称之为容器)&#xff0c;得益于大佬们对于单一职责模式的坚决贯彻&#xff0c;在十几年以来spring的发展路上&#xff0c;扩展出来大量的工厂类&#xff0c;每一个工厂类都承担着自己的功能(其实就是有对应的方法实现)…

Linux 期末复习

Linux 期末复习 计算机历史 硬件基础 1&#xff0c;计算机硬件的五大部件&#xff1a;控制器、运算器、存储器、输入输出设备 2&#xff0c;cpu分为精简指令集(RISC)和复杂指令集(CISC) 3&#xff0c;硬件只认识0和1&#xff0c;最小单位是bit&#xff0c;最小存储单位是字…

【论文阅读】Non-blocking Lazy Schema Changes in Multi-Version

Non-blocking Lazy Schema Changes in Multi-Version Database Management Systems 1. Intro 1.1 Motivation 一个是online能够提供不停机的更新的能力&#xff0c;在很多业务系统里面是必要的。第二个是满足高可用&#xff0c;SaaS、PaaS要提供高可用的系统给用户&#xff…

【Linux实用篇】Linux常用命令(1)

目录 1.1 Linux命令初体验 1.1.1 常用命令演示 1.1.2 Linux命令使用技巧 1.1.3 Linux命令格式 1.2 文件目录操作命令 1.2.1 ls 1.2.2 cd 1.2.3 cat 1.2.4 more 1.2.5 tail 1.2.6 mkdir 1.2.7 rmdir 1.2.8 rm 1.1 Linux命令初体验 1.1.1 常用命令演示 在这一部分中…

openssl3.2 - 官方demo学习 - cms - cms_ver.c

文章目录 openssl3.2 - 官方demo学习 - cms - cms_ver.c概述运行结果笔记END openssl3.2 - 官方demo学习 - cms - cms_ver.c 概述 CMS验签, 将单独签名和联合签名出来的签名文件都试试. 验签成功后, 将签名数据明文写入了文件供查看. 也就是说, 只有验签成功后, 才能看到签名…

如何在你的网站接入QQ登录?

文章目录 准备阶段申请QQ登录的权限创建应用最后上传qqlogin.php代码 准备阶段 国内服务器和备案域名需要你有张独一无二本人的身份证你正面手持身份证的图片一张100px*100px的网站图标 申请QQ登录的权限 首先访问qq互联&#xff0c;点击我直接访问 登陆完成后我们点击面的…

常用计算电磁学算法特性与电磁软件分析

常用计算电磁学算法特性与电磁软件分析 参考网站&#xff1a; 计算电磁学三大数值算法FDTD、FEM、MOM ADS、HFSS、CST 优缺点和应用范围详细教程 ## 基于时域有限差分法的FDTD的计算电磁学算法&#xff08;含Matlab代码&#xff09;-框架介绍 参考书籍&#xff1a;The finite…

代码随想录算法训练营第四天 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II

代码随想录算法训练营第四天 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II 文章目录 代码随想录算法训练营第四天 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II1 Le…