nestjs入门学习规划:https://blog.csdn.net/lxy869718069/article/details/114028195
多对多关系
多对多是一种 A 包含多个 B 实例,而 B 包含多个 A 实例的关系。
例如:
一个问题可能有多种类别,比如:问题A是既是一个地理类又是计算类问题,而一个类别也可以修饰多个问题,比如:地理类问题可以有问题A也可以有问题C。
数据库显示如下:
多对多关系,实际上就是多用一张表来存放两种数据之间的关联关系。
常规的两种多对多方式及其项目解析
第一种方式:typeorm官方的示例进行的多对多关系。
目录结构:
1.建立两个entity并使用@ManyToMany装饰器绑定关系:
表1 category.entity.ts 内容如下:
import {
Column,
Entity,
PrimaryGeneratedColumn,
BaseEntity,
ManyToMany,
} from 'typeorm';
import { Question } from './question.entity';
/* 类别表 */
@Entity()
export class Category extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', name: 'name', comment: '类别名称' })
name: string;
@ManyToMany(() => Question, (question) => question.categories)
questions: Question[];
}
表2 question.entity.ts 内容如下:
import {
Column,
Entity,
PrimaryGeneratedColumn,
BaseEntity,
JoinTable,
ManyToMany,
RelationId,
} from 'typeorm';
import { Category } from './category.entity';
/* 问题表---主表带有@JoinTable */
@Entity()
export class Question extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '标题' })
title: string;
@ManyToMany(() => Category, (category) => category.questions)
@JoinTable()
categories: Category[];
}
解析:这两个实体建立完成之后,将会默认在数据库中生成三张表,虽然只有两个实体,但是确实会生成三张表,其中第三张为关联表,只记录两表之间的关联关系。
@ManyToMany这个方法传递两个参数第一个参数为一个回调函数,直接指向绑定关联的实体类,第二个参数也是一个回调函数,但是包含一个参数,这个参数代指第一个参数中的实体类
因此可以:category.questions 和 question.categories
@JoinTable()通常放置在主要操作的那个表中
2.建立控制层和服务层,进行增删查改操作。
控制层 user.controller.ts 内容如下:
import {
Controller,
Get,
Post,
Delete,
Body,
Query,
Put,
} from '@nestjs/common';
import { Transaction, TransactionManager, EntityManager } from 'typeorm';
import { UserService } from './user.service';
import { Question } from '../entities/question.entity';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
/*
查询所有列表
@Query 无
*/
@Get('list')
findAll(): Promise<Question[]> {
return this.userService.findAll();
}
/*
查询单个详情
@Query ?id=xxx
*/
@Get('detail')
findOne(@Query() query): Promise<Question> {
return this.userService.findOne(query);
}
/*
新增账号和用户信息数据
新增时候由于有可能出现两张表同时进行操作的情况
因此开启事务事件:为了让同时进行的表操作要么一起完成,要么都失败
@Transaction()和 @TransactionManager() manager: EntityManager 是事务的装饰器和对象
@Body
{
"title": "问题5",
"categorylist": [
1,
3
]
}
*/
@Post('add')
@Transaction()
addOne(
@Body() rUser,
@TransactionManager() manager: EntityManager,
): Promise<String> {
return this.userService.addOne(rUser, manager);
}
/*
修改账号和用户信息数据
事务同上
@Body 有数据id则修改否则新增,然后对比数据库数据进行多余的删除
{
"id":3,
"title": "问题555",
"categorylist": [
1,
2
]
}
*/
@Put('update')
@Transaction()
updateOne(
@Body() uUser,
@TransactionManager() manager: EntityManager,
): Promise<String> {
return this.userService.updateOne(uUser, manager);
}
/*
删除数据
事务同上
@Query ?id=xxx
*/
@Delete('del')
@Transaction()
delOne(
@Query() query,
@TransactionManager() manager: EntityManager,
): Promise<String> {
return this.userService.delOne(query, manager);
}
}
这里的内容非常简单,代码中描述应该已经到位了:就是加了四个接口而已。其中涉及到了事务的概念。可以参考文章:
https://editor.csdn.net/md?articleId=114012116
服务层user.service.ts内容如下:
import { Injectable } from '@nestjs/common';
import { getRepository } from 'typeorm';
import { Question } from './../entities/question.entity';
import { Category } from './../entities/category.entity';
/*
使用Repository<>对象执行增删查改的操作
*/
@Injectable()
export class UserService {
constructor() {}
/*
获取所有用户数据列表
*/
async findAll(): Promise<Question[]> {
/*
构建QueryBuilder查询
*/
let list = await getRepository(Question)
.createQueryBuilder('question')
.leftJoinAndSelect('question.categories', 'category')
.getMany();
return list;
}
/*
获取单个用户详情
*/
async findOne(query): Promise<Question> {
let list = await getRepository(Question)
.createQueryBuilder('question')
.leftJoinAndSelect('question.categories', 'category')
.where('question.id = :id', { id: query.id })
.getOne();
return list;
}
/*
新增用户
rUser格式
{
"title": "问题5",
"categorylist": [
1,
3
]
}
*/
async addOne(rUser, manager): Promise<String> {
let lists = []; // 用于保存拥有的categories
// 目的用于获取关联信息相关的内容
if (Object.keys(rUser.categorylist).length != 0) {
for (let i = 0; i < rUser.categorylist.length; i++) {
let listOne = await manager.findOne(Category, {
id: rUser.categorylist[i],
});
lists.push(listOne);
}
}
const question = new Question();
question.title = rUser.title;
// 此处为关联表内容新增的关键
question.categories = lists;
await manager.save(Question, question);
return '新增成功!';
}
/*
修改用户
uUser格式
{
"id":3,
"title": "问题555",
"categorylist": [
1,
2
]
}
*/
async updateOne(uUser, manager): Promise<String> {
let lists = []; // 用于保存拥有的categories
// 目的用于获取关联信息相关的内容
if (Object.keys(uUser.categorylist).length != 0) {
for (let i = 0; i < uUser.categorylist.length; i++) {
let listOne = await manager.findOne(Category, {
id: uUser.categorylist[i],
});
lists.push(listOne);
}
}
const question = new Question();
// 此处给了id并且categories赋了新内容,这样就会自动更新关联表中的数据(删除旧的新增新的)
question.id = uUser.id;
question.title = uUser.title;
// 此处为关联表内容新增的关键
question.categories = lists;
await manager.save(Question, question);
return '修改成功!';
}
/*
删除用户
*/
async delOne(query, manager): Promise<String> {
// 删除时候关联信息会自动删除
await manager.delete(Question, { id: query.id });
return '删除成功!';
}
}
解读:单个查询和总体查询就不多做描述,仅仅只是用了关联查询而已。
新增和修改则是直接关联起来两者即可。如: question.categories = lists;,并且关联表会自动进行数据改变。
删除同样是如此,以带有@JoinTable()这个装饰器的实体类为主要操作对象。
注意:记得在user.module.ts中注册
user.module.ts内容如下:
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Category } from './../entities/category.entity';
import { Question } from './../entities/question.entity';
@Module({
imports: [TypeOrmModule.forFeature([Question, Category])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
第二种方式:主动建立关联实体类来记录两表的关联关系
目录结构:
注意:这里是有三个实体类的。
1.建立三个entity,通过第三方实体类来相互链接。
实体1 :category.entity.ts 内容为:
import { Column, Entity, PrimaryGeneratedColumn, BaseEntity } from 'typeorm';
/* 类别表 */
@Entity()
export class Category extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', name: 'type', comment: '类别名' })
type: string;
@Column({ type: 'varchar', name: 'value', comment: '类别值' })
value: string;
}
实体2 :question.entity.ts 内容为:
import { Column, Entity, PrimaryGeneratedColumn, BaseEntity } from 'typeorm';
/* 问题表 */
@Entity()
export class Question extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({
comment: '标题',
name: 'title',
})
title: string;
@Column({
comment: '描述',
name: 'description',
nullable: true,
})
description: string;
}
实体3 :question_category.entity.ts 内容为:
import { Column, Entity, PrimaryGeneratedColumn, BaseEntity } from 'typeorm';
/* 问题和类别---关联表 */
@Entity()
export class QuestionCategory extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({
comment: '问题id',
name: 'questionId',
})
questionId: string;
@Column({
comment: '类别id',
name: 'categoryId',
})
categoryId: string;
}
仔细观察上面三个实体的关系会知道,question_category.entity就是前两者的关联内容,但是他们之间并没有用@ManyToMany来进行绑定,甚至category.entity和question.entity看不出有任何的关联关系。
但我们就是利用question_category.entity 来进行多对多的数据保存于处理。这样的好处是能够让实体类与表进行完整的一一对应。我更喜欢这种。
2.建立控制层和服务层,进行增删查改操作。
控制层 user.controller.ts 内容如下:
import {
Controller,
Get,
Post,
Delete,
Body,
Query,
Put,
} from '@nestjs/common';
import { Transaction, TransactionManager, EntityManager } from 'typeorm';
import { UserService } from './user.service';
import { Question } from '../entities/question.entity';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
/*
查询所有列表
@params 无
*/
@Get('list')
findAll(): Promise<Question[]> {
return this.userService.findAll();
}
/*
查询单个详情
@Query ?id=xxx
*/
@Get('detail')
findOne(@Query() query): Promise<Question> {
return this.userService.findOne(query);
}
/*
新增账号和用户信息数据
新增时候由于有可能出现两张表同时进行操作的情况
因此开启事务事件:为了让同时进行的表操作要么一起完成,要么都失败
@Transaction()和 @TransactionManager() manager: EntityManager 是事务的装饰器和对象
@Body :注意先给category表随便加三条数据
{
"title": "问题1",
"description": "这是1号问题的描述",
"list": [1,3]
}
}
*/
@Post('add')
@Transaction()
addOne(
@Body() rUser,
@TransactionManager() manager: EntityManager,
): Promise<String> {
return this.userService.addOne(rUser, manager);
}
/*
修改账号和用户信息数据
事务同上
@Body
{
"id": 1,
"title": "问题1.111",
"description": "问题1.111的描述",
"list": [1,2]
}
*/
@Put('update')
@Transaction()
updateOne(
@Body() uUser,
@TransactionManager() manager: EntityManager,
): Promise<String> {
return this.userService.updateOne(uUser, manager);
}
/*
删除数据
事务同上
@Query ?id=xxx
*/
@Delete('del')
@Transaction()
delOne(
@Query() query,
@TransactionManager() manager: EntityManager,
): Promise<String> {
return this.userService.delOne(query, manager);
}
}
解析:控制层的内容和第一种方式基本上没什么区别,都是为了定义几个接口名称和事务而已。
服务层 user.service.ts 的内容如下:
import { Injectable } from '@nestjs/common';
import { getRepository } from 'typeorm';
import { Question } from './../entities/question.entity';
import { Category } from './../entities/category.entity';
import { QuestionCategory } from './../entities/question_category.entity';
/*
使用Repository<>对象执行增删查改的操作
*/
@Injectable()
export class UserService {
constructor() {}
/*
获取所有用户数据列表
*/
async findAll(): Promise<Question[]> {
// 构建QueryBuilder查询
/*
1.Question、Category,QuestionCategory 指的是实体类的名称
2.innerJoin()是对关联表进行关联,quescate,question,category都是别名
------ 三个参数含义分别是:实体对象,别名,关联关系
3.innerJoinAndMapMany()中的question.list是为了给question增加一个list字段用于保存Category的所有内容
------ 四个参数含义分别是:展示列表名,实体对象,别名,关联关系
*/
let list = await getRepository(Question)
.createQueryBuilder('question')
.innerJoin(
QuestionCategory,
'quescate',
'question.id = quescate.questionId',
)
.innerJoinAndMapMany(
'question.list',
Category,
'category',
'category.id = quescate.categoryId',
)
.getMany();
return list;
/*
另一种使用getManager().query("sql语句")执行原生sql操作即可。
补充:联表查询建议使用QueryBuilder自由构建出所需要的查询内容
*/
}
/*
获取单个用户详情
*/
async findOne(query): Promise<Question> {
let list = await getRepository(Question)
.createQueryBuilder('question')
.innerJoin(
QuestionCategory,
'quescate',
'question.id = quescate.questionId',
)
.innerJoinAndMapMany(
'question.list',
Category,
'category',
'category.id = quescate.categoryId',
)
.where('question.id = :id', { id: query.id })
.getOne();
return list;
}
/*
新增用户
rUser格式:注意先给category表随便加三条数据
{
"title": "问题1",
"description": "这是1号问题的描述",
"list": [1,3]
}
*/
async addOne(rUser, manager): Promise<String> {
// 先保存问题的数据
let question = new Question();
question.title = rUser.title;
question.description = rUser.description;
const que = await manager.save(Question, question);
if (Object.keys(rUser.list).length != 0) {
// 后保存关联表的数据
for (let i = 0; i < rUser.list.length; i++) {
let qescat = new QuestionCategory();
qescat.categoryId = rUser.list[i];
qescat.questionId = que.id;
await manager.save(QuestionCategory, qescat);
}
return '新增成功!';
}
}
/*
修改用户:有数据id则修改否则新增,然后对比数据库数据进行多余的删除
uUser格式
{
"id": 1,
"title": "问题1.111",
"description": "问题1.111的描述",
"list": [1,2]
}
*/
async updateOne(uUser, manager): Promise<String> {
// 先修改问题表的数据
let question = new Question();
question.id = uUser.id;
question.title = uUser.title;
question.description = uUser.description;
await manager.update(Question, { id: question.id }, question);
// 在根据问题的id删除关联表中对应的数据
await manager.delete(QuestionCategory, { questionId: uUser.id });
// 之后在将新的关联数据添加进关联表
for (let i = 0; i < uUser.list.length; i++) {
let qescat = new QuestionCategory();
qescat.categoryId = uUser.list[i];
qescat.questionId = uUser.id;
await manager.save(QuestionCategory, qescat);
}
return '修改成功!';
}
/*
删除用户
*/
async delOne(query, manager): Promise<String> {
// 先删除关联表内容
await manager.delete(QuestionCategory, { questionId: query.id });
// 然后在删除主表内容
await manager.delete(Question, { id: query.id });
return '删除成功!';
}
}
解析:
查询使用了 innerJoin 和 innerJoinAndMapMany这两个方法,这是关联查询的意思,用于关联第三张表,然后紧接着查询第二张表,这样三张表都能进行联动。具体方式代码有说明。
新增则是根据用户提交的数据,先新增主要的表数据,然后循环保存到关联表中去。分成两步来做,
修改则是修改完主数据之后,删除该主数据id在关联表中的内容,然后再把全新的关联关系新增。
删除则是先删除关联表中的管理数据,然后再删除主表的数据。
而 user.module.ts 中的内容如下:
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Category } from './../entities/category.entity';
import { Question } from './../entities/question.entity';
@Module({
imports: [TypeOrmModule.forFeature([Question, Category])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
基本上和第一种方法一样,这里同样不需要注册第三方的关联表