ToB企服应用市场:ToB评测及商务社交产业平台
标题:
NestJS【实战】操纵数据库(含集成 prisma,创建表,添加数据校验,数据的
[打印本页]
作者:
火影
时间:
昨天 23:32
标题:
NestJS【实战】操纵数据库(含集成 prisma,创建表,添加数据校验,数据的
单库 or 多库 ?
方案长处缺点适用场景单库成本低
开发摆设快捷性能受限
扩展性有限
故障风险数据量适中
安全性需求不高多库可高度自定义架构复杂,摆设和维护成本较高
数据一致性题目
分布式事务处理较为复杂自定义需求高
数据量大
安全性需求高
合规合法要求(物理隔离)
ORM 库选型
需访问多种关系数据库,且数据库版本比较低,则选择 TypeORM
需访问的数据库版本比较高,优先选择 Prisma
仅需访问 MongoDB ,优先选择 Mongoose
集成 ORM 库 – prisma
安装 prisma
pnpm i -D 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
nest g mo prisma
复制代码
创建 prisma 的 service
nest g s prisma --no-spec
复制代码
--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
升序 asc
降序 desc
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企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4