nestjs[typeorm学习之多对多表关系探究与使用]

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 {}


基本上和第一种方法一样,这里同样不需要注册第三方的关联表

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
nestjs可以和typeorm配合使用typeorm是一个支持多种数据库的ORM框架,可以简化数据库的操作。下面是nestjs如何使用typeorm的步骤: 1. 安装typeorm和相应的数据库驱动,例如mysql, postgres等。可以使用npm命令进行安装: ``` npm install typeorm mysql ``` 2. 在nestjs中创建一个数据库模块,可以在模块中配置数据库连接等信息。例如: ```typescript import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './user.entity'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [User], synchronize: true, }), ], }) export class DatabaseModule {} ``` 3. 创建一个实体类,示数据库中的一个格。例如: ```typescript import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() age: number; @Column() email: string; } ``` 4. 在nestjs的服务中使用typeorm进行数据库操作。例如: ```typescript import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; @Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} async findAll(): Promise<User[]> { return this.userRepository.find(); } async create(user: User): Promise<User> { return this.userRepository.save(user); } } ``` 这样,nestjs就可以和typeorm配合使用进行数据库操作了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值