单库 or 多库 ?
方案长处缺点适用场景单库成本低
开发摆设快捷性能受限
扩展性有限
故障风险数据量适中
安全性需求不高多库可高度自定义架构复杂,摆设和维护成本较高
数据一致性题目
分布式事务处理较为复杂自定义需求高
数据量大
安全性需求高
合规合法要求(物理隔离) ORM 库选型
- 需访问多种关系数据库,且数据库版本比较低,则选择 TypeORM
- 需访问的数据库版本比较高,优先选择 Prisma
- 仅需访问 MongoDB ,优先选择 Mongoose
集成 ORM 库 – prisma
安装 prisma
vscode 安装官方插件
初始化 prisma
- npx prisma init --datasource-provider postgresql
复制代码
- 使用的 postgresql 数据库
- 初始化后,会生成文件 .env 和 prisma\schema.prisma
修改 .env
- DATABASE_URL="postgresql://数据库用户名:密码@数据库IP:数据库端口/数据库名称?schema=public"
复制代码 最终范比方下:
- DATABASE_URL="postgresql://testuser:test6666@localhost:5432/testdb?schema=public"
复制代码 package.json 中新增脚本
- "prisma:generate": "prisma generate",
- "prisma:pull-DB": "prisma db pull",
- "prisma:push-DB": "prisma db push",
- "prisma:migrate-create": "prisma migrate dev",
- "prisma:migrate-deploy": "prisma migrate deploy",
复制代码
- prisma generate 用于安装 Prisma Client
- prisma db push 用于在开发情况中,将项目中表定义的改动,同步到数据库中
- prisma db pull 用于在开发情况中,将数据库的表设定,同步到项目中
- prisma migrate dev 用于在开发情况,将项目中表定义的改动,同步到数据库中,并在项目中生成一条操纵记录
- prisma migrate deploy 用于在生产情况,将项目中表定义的改动,摆设到数据库中
其他下令:
prisma migrate reset 用于重置数据库,会删除数据库中的所有数据并重新应用所有迁移,通常用于开发过程中快速重置数据库状态。
prisma\schema.prisma 中定义表
- model Blog {
- id Int @id @default(autoincrement())
- title String
- content String
- author String?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- @@map("blogs")
- }
复制代码
- Blog 为表模型名称
- @id 声明为 id 字段
- @default 指定默认值
- autoincrement() 自增
- ? 可选
- now() 当前时间
- @updatedAt 更新时更新
- @@map 自定义数据库表名称
实验脚本 prisma generate 安装 Prisma Client
实验脚本 prisma migrate dev 根据 prisma\schema.prisma 中定义的表在数据库中创建数据表
扣问输入一个镜像名称时,输入init 即可
实验成功后,在数据库中,可见表 blogs
创建 prisma 的 module
创建 prisma 的 service
- nest g s prisma --no-spec
复制代码
修改 src\prisma\prisma.service.ts 的内容为
- import { Injectable } from '@nestjs/common';
- import { PrismaClient } from '@prisma/client';
- @Injectable()
- export class PrismaService extends PrismaClient {}
复制代码 修改 src\prisma\prisma.module.ts 的内容为
- import { Module } from '@nestjs/common';
- import { PrismaService } from './prisma.service';
- @Global() // 声明为全局可用
- @Module({
- providers: [PrismaService],
- exports: [PrismaService],
- })
- export class PrismaModule {}
复制代码 创建表
prisma\schema.prisma 中定义表
- model Diary {
- id Int @id @default(autoincrement())
- title String
- content String
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- }
复制代码 实验脚本 "prisma migrate dev" (此时不能启动项目,否则无法 Generated Prisma Client)
输入本次操纵的名称后回车,即可在数据库中看到创建好的表。
一对多
以 课程类型 vs 课程 为例
- 每个课程类型对应多个课程
- 每个课程只属于一种课程类型
prisma\schema.prisma
- // 课程--如 vue实战开发商城
- model Course {
- id Int @id @default(autoincrement())
- title String @unique
- // 链接到课程详情
- url String?
- // 【一对一】课程 vs 课程类型(每个课程只属于一种课程类型)
- typeId Int?
- // 【外链】课程类型 -- 本表的 typeId 外链 CourseType 表的 id
- type CourseType? @relation(fields: [typeId], references: [id])
- }
- // 课程类型-- 如前端、后端
- model CourseType {
- id Int @id @default(autoincrement())
- name String @unique
- // 【一对多】课程类型 vs 课程(每个课程类型有多个课程)
- courses Course[]
- }
复制代码 添加数据校验
安装相关依靠
- pnpm i --save class-validator class-transformer
复制代码- pnpm i --save @nestjs/mapped-types
复制代码 全局导入使用 src\main.ts 的 app.listen 上添加
- app.useGlobalPipes(
- new ValidationPipe({
- transform: true, // 全局启用 transform
- }),
- );
复制代码 同一报错处理
src\common\errHandle.ts
- import { HttpException } from '@nestjs/common';
- import { Prisma } from '@prisma/client';
- export function errHandler(error: any) {
- let message = '未知错误';
- let status = 500;
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
- message = String(error.meta?.cause);
- // 不同的错误代码可以有不同的处理逻辑
- switch (error.code) {
- case 'P2002':
- message = '数据已存在,请勿重复添加';
- status = 409;
- break;
- case 'P2025':
- message = '操作的数据不存在';
- break;
- default:
- message = error.message;
- }
- } else if (error instanceof Prisma.PrismaClientValidationError) {
- // 数据验证错误,通常是由于输入的数据不满足模型的约束条件
- message = error.message;
- status = 400;
- const regex = /Argument `(\w+)` is missing/;
- const match = message.match(regex);
- if (match) {
- message = `缺失参数 ${match[1]}`;
- }
- const regex2 = /Argument `(\w+)`: Invalid value provided.(.*)\./;
- const match2 = message.match(regex2);
- if (match2) {
- message = `非法参数 ${match2[1] + ':' + match2[2]} `;
- }
- } else if (error instanceof Prisma.PrismaClientInitializationError) {
- // 初始化错误,通常是由于数据库连接问题
- message = 'Prisma 初始化错误:' + error.message;
- } else if (error.message.includes('Foreign key constraint failed')) {
- message = '外键约束冲突,请检查关联数据';
- }
- throw new HttpException(message, status);
- }
复制代码 使用范例
- import { errHandler } from '@/common/errHandle';
复制代码- create_courseType(dto: Create_CourseType_Dto) {
- return this.prisma.courseType.create({ data: dto }).catch((err) => {
- errHandler(err);
- });
- }
复制代码 新增数据
新建文件 src\modules\diary\dto.create.ts
- import { IsString, IsOptional, IsNotEmpty } from 'class-validator';
- export class CreateDto {
- @IsString()
- @IsNotEmpty()
- title: string;
- @IsString()
- @IsOptional()
- content: string;
- }
复制代码
- @IsString() 校验是否为字符串
- @IsNotEmpty() 校验不能为空
- @IsOptional() 校验可选
src\modules\diary\diary.controller.ts
- // 对应的接口为 /api/v1/dairy/create 在body中传入json数据
- @Post('create')
- create(@Body() dto: CreateDto) {
- return this.service.create(dto);
- }
复制代码 src\modules\diary\diary.service.ts
- create(dto: CreateDto) {
- return this.prisma.diary.create({ data: dto });
- }
复制代码 批量新增
src\modules\course\dto.courseType.ts
- export class Create_CourseType_Dto {
- @IsString()
- @IsNotEmpty()
- name: string;
- }
- export class Batch_Create_CourseType_Dto {
- @ValidateNested()
- @Type(() => Create_CourseType_Dto)
- CourseTypeList: Create_CourseType_Dto[];
- }
复制代码 src\modules\course\course.controller.ts
- // 对应的接口为 /api/v1/course/courseType/batch_create 在body中传入json数据
- @Post('courseType/batch_create')
- batch_create_courseType(@Body() dto: Batch_Create_CourseType_Dto) {
- return this.service.batch_create_courseType(dto);
- }
复制代码 src\modules\course\course.service.ts
- batch_create_courseType(dto: Batch_Create_CourseType_Dto) {
- return this.prisma.courseType
- .createMany({ data: dto.CourseTypeList })
- .catch((err) => {
- errHandler(err);
- });
- }
复制代码 删除数据
src\modules\diary\diary.controller.ts
- // 对应的接口为 /api/v1/dairy/del?id=目标id id必传
- @Delete('del')
- delete(@Query('id', ParseIntPipe) id: number) {
- return this.service.delete(id);
- }
复制代码 src\modules\diary\diary.service.ts
- delete(id: number) {
- return this.prisma.diary
- .delete({
- where: { id },
- })
- .catch((err) => {
- throw new NotFoundException(err.meta ? err.meta?.cause : '未知错误');
- });
- }
复制代码 删除的数据不存在时,返回报错信息
删除成功时,返回删除的数据
修改数据
新建文件 src\modules\diary\dto.update.ts
- import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
- import { CreateDto } from './dto.create';
- import { OmitType } from '@nestjs/mapped-types';
- export class UpdateDto extends OmitType(CreateDto, ['title']) {
- @IsNotEmpty()
- @IsNumber()
- id: number;
- @IsString()
- @IsNotEmpty()
- @IsOptional()
- title: string;
- }
复制代码
- extends 继续 CreateDto ,免去重复誊写字段
- OmitType 用于移除 CreateDto 中 title 的校验设置
- @IsNumber() 用于校验是否为数字
src\modules\diary\diary.controller.ts
- // 对应的接口为 /api/v1/dairy/update 在body中传入json数据,id必传
- @Put('update')
- update(@Body() dto: UpdateDto) {
- return this.service.update(dto);
- }
复制代码 src\modules\diary\diary.service.ts
- update(dto: UpdateDto) {
- return this.prisma.diary
- .update({
- where: { id: dto.id },
- data: dto,
- })
- .catch((err) => {
- throw new NotFoundException(err.meta ? err.meta?.cause : '未知错误');
- });
- }
复制代码 更新的数据不存在时,返回报错信息
更新成功时,返回更新后的数据
查询数据
查询多条数据(含分页参数校验)
src\common\dto\pagination.dto.ts
- import { Type } from 'class-transformer';
- import { IsNumber } from 'class-validator';
- export class PaginationDto {
- @IsNumber()
- @Type(() => Number)
- page: number = 1;
- @IsNumber()
- @Type(() => Number)
- size: number = 10;
- }
复制代码 src\modules\course\dto.course.ts
- import { PaginationDto } from '@/common/dto/pagination.dto';
- export class GetList_Course_Dto extends PaginationDto {
- @IsString()
- @IsOptional()
- title: string;
- }
复制代码 src\modules\course\course.controller.ts
- // 对应的接口为 /api/v1/course/list
- @Get('list')
- async get_course_list(@Query() query: GetList_Course_Dto) {
- let [data, total] = await this.service.get_course_list(query);
- // 数据脱敏
- data = data.map((item) => {
- // 删除返回的id字段
- delete item.id;
- return item;
- });
- return {
- data,
- total,
- 'current-page': query.page,
- 'page-size': query.size,
- };
- }
复制代码 src\modules\course\course.service.ts
- async get_course_list(
- query: GetList_Course_Dto,
- ): Promise<[Course[], number]> {
- const skip = (query.page - 1) * query.size;
- const take = query.size;
- return await this.prisma.$transaction([
- // 分页查询
- this.prisma.course.findMany({
- skip,
- take,
- where: {
- // 模糊查询
- title: {
- contains: query.title,
- },
- },
- orderBy: [
- {
- // 按创建时间倒序(最新创建的数据在最前面)
- createdAt: 'desc',
- },
- {
- // 按标题正序
- title: 'asc',
- },
- ],
- }),
- // 查询总数
- this.prisma.course.count(),
- ]);
- }
复制代码
- $transaction 为事务:一连实验多个操纵,若操纵失败则会回滚。
根据外键 id 联查外表数据 include
如根据课程中的 typeId 查询课程类型信息
- // 分页查询
- this.prisma.course.findMany({
- skip,
- take,
- include: {
- // 包含课程类型信息
- type: true,
- },
- }),
复制代码 查询效果如下:
- {
- "data": [
- {
- "id": 1,
- "title": "vue实战开发商城",
- "url": null,
- "typeId": 1,
- "type": {
- "id": 1,
- "name": "前端"
- }
- },
- {
- "id": 2,
- "title": "react 实战开发后台管理系统",
- "url": null,
- "typeId": 1,
- "type": {
- "id": 1,
- "name": "前端"
- }
- },
- {
- "id": 3,
- "title": "nextJS 实战开发后台管理系统",
- "url": null,
- "typeId": 2,
- "type": {
- "id": 2,
- "name": "后端"
- }
- }
- ],
- "total": 3,
- "current-page": 1,
- "page-size": 10
- }
复制代码 排序 orderBy
- this.prisma.course.findMany({
- skip,
- take,
- orderBy: {
- // 按照创建时间倒序(最新创建的数据在最前面)
- createdAt: 'desc',
- },
- }),
复制代码 多字段排序
- orderBy: [
- {
- // 按创建时间倒序(最新创建的数据在最前面)
- createdAt: 'desc',
- },
- {
- // 按标题正序
- title: 'asc',
- },
- ],
复制代码 搜索条件 where
https://blog.csdn.net/weixin_41192489/article/details/145450412
实战范例
src\modules\diary\diary.controller.ts
- import { Body, Controller, DefaultValuePipe, Delete, Get, ParseIntPipe, Post, Put, Query,} from '@nestjs/common';import { DiaryService } from './diary.service';import { CreateDto } from './dto.create';import { UpdateDto } from './dto.update';@Controller('diary')export class DiaryController { constructor(private service: DiaryService) {} // 对应的接口为 /api/v1/dairy/list @Get('list') async getList( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('size', new DefaultValuePipe(10), ParseIntPipe) size: number, ) { const [data, total] = await this.service.findMany(page, size); return { data, total, 'current-page': page, 'page-size': size, }; } // 对应的接口为 /api/v1/dairy/create 在body中传入json数据
- @Post('create')
- create(@Body() dto: CreateDto) {
- return this.service.create(dto);
- }
- // 对应的接口为 /api/v1/dairy/update 在body中传入json数据,id必传
- @Put('update')
- update(@Body() dto: UpdateDto) {
- return this.service.update(dto);
- }
- // 对应的接口为 /api/v1/dairy/del?id=目标id id必传
- @Delete('del')
- delete(@Query('id', ParseIntPipe) id: number) {
- return this.service.delete(id);
- }
- }
复制代码 src\modules\diary\diary.module.ts
- import { Module } from '@nestjs/common';
- import { DiaryService } from './diary.service';
- import { DiaryController } from './diary.controller';
- @Module({
- providers: [DiaryService],
- controllers: [DiaryController],
- })
- export class DairyModule {}
复制代码 src\modules\diary\diary.service.ts
- import { Injectable, NotFoundException } from '@nestjs/common';import { Diary } from '@prisma/client';import { PrismaService } from 'src/prisma/prisma.service';import { CreateDto } from './dto.create';import { UpdateDto } from './dto.update';@Injectable()export class DiaryService { constructor(private prisma: PrismaService) {} async findMany(page: number, size: number): Promise<[Diary[], number]> { const skip = (page - 1) * size; const take = size; return await this.prisma.$transaction([ // 分页查询 this.prisma.diary.findMany({ skip, take, }), // 查询总数 this.prisma.diary.count(), ]); } create(dto: CreateDto) {
- return this.prisma.diary.create({ data: dto });
- }
- update(dto: UpdateDto) {
- return this.prisma.diary
- .update({
- where: { id: dto.id },
- data: dto,
- })
- .catch((err) => {
- throw new NotFoundException(err.meta ? err.meta?.cause : '未知错误');
- });
- }
- delete(id: number) {
- return this.prisma.diary
- .delete({
- where: { id },
- })
- .catch((err) => {
- throw new NotFoundException(err.meta ? err.meta?.cause : '未知错误');
- });
- }
- }
复制代码 src\modules\diary\dto.create.ts
- import { IsString, IsOptional, IsNotEmpty } from 'class-validator';
- export class CreateDto {
- @IsString()
- @IsNotEmpty()
- title: string;
- @IsString()
- @IsOptional()
- content: string;
- }
复制代码 src\modules\diary\dto.update.ts
- import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
- import { CreateDto } from './dto.create';
- import { OmitType } from '@nestjs/mapped-types';
- export class UpdateDto extends OmitType(CreateDto, ['title']) {
- @IsNotEmpty()
- @IsNumber()
- id: number;
- @IsString()
- @IsNotEmpty()
- @IsOptional()
- title: string;
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |