- 创建项目
- Nest 控制器
- nest 配置路由请求数据
- Nest 服务
- Nest 模块
- 配置静态资源
- 配置模板引擎
- Cookie 的使用
- Session 的使用
- 跨域,前缀路径、网站安全、请求限速
- 管道、守卫、拦截器、过滤器、中间件
- 一例看懂中间件、守卫、管道、异常过滤器、拦截器
- 数据验证
- 配置抽离
- 环境配置
- 文件上传与下载
- 实现图片随机验证码
- 邮件服务
- nest 基于 possport + jwt 做登陆验证
- 对数据库的密码加密:md5 和 bcryptjs
- 角色权限
- 定时任务
- 接入 Swagger 接口文档
- nest 连接 Mongodb
- typeORM 操作 Mysql 数据库
- nest 统一处理数据库操作的查询结果
- 数据库实体设计与操作
- typeorm 增删改查操作
- typeorm 使用事务的 3 种方式
- typeorm 一对一关系设计与增删改查
- typeorm 一对多和多对一关系设计与增删改查
- typeorm 多对多关系设计与增删改查
- nest 连接 Redis
- 集成 redis 实现单点登录
- Q:nestJS 注入其他依赖时为什么还需要导入其 module
typeorm 多对多关系设计与增删改查
实体如何设计多对多关系?如何增删改查?
多对多关系
定义:多对多是一种 A 包含多个 B,而 B 包含多个 A 的关系 如:一个粉丝可以关注多个主播,一个主播可以有多个粉丝 如:一篇文章属于多个分类,一个分类下有多篇文章 比如这篇文章,可以放在 nest 目录,也可以放在 typeorm 目录或者 mysql 目录
实现方式
第一种:建立两张表,使用装饰器 @ManyToMany
建立关系, typeorm
会自动生成三张表 第二种:手动建立 3 张表
这里使用第一种
实体设计
这里将设计一个用户(粉丝) 与 明星的 多对多关系
用户(粉丝)可以主动关注明星,让 users
变为主表,加入 @JoinTable()
使用 @ManyToMany()
来建立多对多关系 第一个参数: () => StarEntity
, 和谁建立关系? 和 StarEntity
建立关系 第二个参数: (star) => star.photo
,和哪个字段联立关系? star
就是 StarEntity
的别名,可随便写,和 PhotoEntity
的 followers
字段建立关系
用户(粉丝)表:follows 关注/跟随
import { Column, Entity, PrimaryGeneratedColumn, ManyToMany, JoinTable, } from 'typeorm'; import { AvatarEntity } from './avatar.entity'; @Entity({ name: 'users' }) export class UsersEntity { @PrimaryGeneratedColumn() id: number; @Column() username: string; @Column() password: string; @ManyToMany(() => StarEntity, (star) => star.followers) @JoinTable() follows: StarEntity[]; 注意这里是数组类型 }
明星表:followers 跟随者
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'; import { UsersEntity } from './user.entity'; @Entity({ name: 'star' }) export class StarEntity { @PrimaryGeneratedColumn() id: number; @Column({ type: 'varchar' }) name: string; @ManyToMany(() => UsersEntity, (user) => user.follows) followers: UsersEntity; }
注意:
程序运行后,将会默认在数据库中生成三张表,users,star,users_follows_star,users_follows_star 是中间表,用于记录 users 和 star 之间的多对多关系,它是自动生成的。
为了测试方便,你可以在 users 表和 star 表创建一些数据:这些属于单表操作
多对多增删改查
只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一 注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
user.controller.ts
import { Controller, Get, Post, Body, Patch, Query, Param, Delete, HttpCode, HttpStatus, ParseIntPipe, } from '@nestjs/common'; import { Transaction, TransactionManager, EntityManager } from 'typeorm'; 开启事务第一步:引入 import { UserService } from './user.service-oto'; @Controller('user') export class UserController { constructor(private readonly userService: UserService) { } @Get() async findAll() { const [data, count] = await this.userService.findAll(); return { count, data }; } @Get(':id') async findOne(@Param('id', new ParseIntPipe()) id: number) { return await this.userService.findOne(id); } @Post(':id') @HttpCode(HttpStatus.OK) @Transaction() 开启事务第二步:装饰接口 async create( @Param('id', new ParseIntPipe()) id: number, @TransactionManager() maneger: EntityManager, 开启事务第三步:获取事务管理器 ) { return await this.userService.create(id, maneger); 开启事务第四步:传递给 service,使用数据库时调用 } @Patch(':id') @Transaction() async update( @Param('id', new ParseIntPipe()) id: number, @TransactionManager() maneger: EntityManager, ) { return await this.userService.update(id, maneger); } @Delete(':id') @Transaction() async remove( @Param('id', new ParseIntPipe()) id: number, @TransactionManager() maneger: EntityManager, ) { return await this.userService.remove(id, maneger); } }
user.service.ts
import { Injectable } from '@nestjs/common'; import { Repository, EntityManager } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; import { UsersEntity } from './entities/user.entity'; import { StarEntity } from './entities/star.entity'; import { ToolsService } from '../../utils/tools.service'; @Injectable() export class UserService { constructor( @InjectRepository(UsersEntity) private readonly usersRepository: Repository<UsersEntity>, @InjectRepository(StarEntity) private readonly starRepository: Repository<StarEntity>, ) { } 一对多增删改查 async findAll() { // return await this.usersRepository.findAndCount({ relations: ['follows'] }); const list = await this.usersRepository .createQueryBuilder('UsersEntity') .leftJoinAndSelect('UsersEntity.follows', 'StarEntity.followers') .getManyAndCount(); return list; } 根据主表 id 查找多对多 async findOne(id: number) { 查询一个用户关注了哪些明星 // const result = await this.usersRepository.findOne(id, { // relations: ['follows'], // }); // if (!result) ToolsService.fail('用户 id 不存在'); // return result; 查询一个明星有多少粉丝 const result = await this.starRepository.findOne(id, { relations: ['followers'], }); if (!result) ToolsService.fail('明星 id 不存在'); return result; } 根据主表 id 创建多对多 粉丝关注明星 async create(id: number, manager: EntityManager) { 要关注的明星 id 数组 const willFollow = [3, 4]; const user = await this.usersRepository.findOne({ id }); if (!user) ToolsService.fail('用户 id 不存在'); if (willFollow.length !== 0) { const followList = []; for (let i = 0; i < willFollow.length; i++) { const star = await manager.findOne(StarEntity, { id: willFollow[i], }); if (!star) ToolsService.fail('主播 id 不存在'); followList.push(star); } const userEntity = new UsersEntity(); 重点: 不指定 id 是创建新的用户,还需要填写 username 和 password 等必填的字段 指定 id 就是更新某些字段:只关注明星,不创建新的用户,同样可用于修改 userEntity.id = id; userEntity.follows = followList; 建立关联,数据表会自动更新 await manager.save(userEntity); } return '新增成功'; } 根据主表 id 更改多对多 假设:某用户关注了 id 为[3, 4]的明星, 现在修改为只关注[2] 逻辑和创建一样 async update(id: number, manager: EntityManager) { const willFollow = [2]; const user = await this.usersRepository.findOne({ id }); if (!user) ToolsService.fail('用户 id 不存在'); if (willFollow.length !== 0) { const followList = []; for (let i = 0; i < willFollow.length; i++) { const listOne = await manager.findOne(StarEntity, { id: willFollow[i], }); if (!listOne) ToolsService.fail('主播 id 不存在'); followList.push(listOne); } const userEntity = new UsersEntity(); userEntity.id = id; userEntity.follows = followList; await manager.save(userEntity); } return '更新成功'; } 根据主表 id 删除多对多 多种删除 async remove(id: number, manager: EntityManager): Promise<any> { const user = await this.usersRepository.findOne(id, { relations: ['follows'], }); if (!user) ToolsService.fail('用户 id 不存在'); 根据 id 删除一个:取消关注某个明星,明星 id 应由前端传递过来,这里写死 需要获取当前用户的的 follows,使用关系查询 const willDeleteId = 2; if (user.follows.length !== 0) { 过滤掉要删除的数据,再重新赋值 const followList = user.follows.filter((star) => star.id != willDeleteId); const userEntity = new UsersEntity(); userEntity.id = id; userEntity.follows = followList; await manager.save(userEntity); } 全部删除关联数据,不删用户 // const userEntity = new UsersEntity(); // userEntity.id = id; // userEntity.follows = []; // await manager.save(userEntity); 如果连用户一起删,会将关联数据一起删除 // await manager.delete(UsersEntity, id); return '删除成功'; } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论