如何用TypeORM与Nest.js处理多对多关系及代码测试?
Nest.js + TypeORM 多对多关联(Player-Nation)逻辑校验与测试指南
一、实体逻辑校验
Player 实体
确保多对多装饰器配置正确,指定中间表、关联字段及反向关联:
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm'; import { Nation } from './nation.entity'; @Entity() export class Player { @PrimaryGeneratedColumn() id: number; @Column() name: string; // 多对多关联配置,JoinTable 必须加在拥有方(Player 或 Nation 二选一) @ManyToMany(() => Nation, nation => nation.players) @JoinTable({ name: 'player_nation', // 中间表名称,需和自动生成的表一致 joinColumn: { name: 'player_id', referencedColumnName: 'id' }, inverseJoinColumn: { name: 'nation_id', referencedColumnName: 'id' } }) nations: Nation[]; }
Nation 实体
反向关联需匹配Player的关联字段:
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'; import { Player } from './player.entity'; @Entity() export class Nation { @PrimaryGeneratedColumn() id: number; @Column() name: string; @ManyToMany(() => Player, player => player.nations) players: Player[]; }
检查点:中间表player_nation需包含player_id和nation_id两个外键字段,且已通过TypeORM的自动同步或迁移生成。
二、DTO 逻辑校验
创建Player的DTO需接收关联的Nation ID数组,避免直接传递实体对象:
import { IsString, IsArray, IsNumber } from 'class-validator'; export class CreatePlayerDto { @IsString() name: string; @IsArray() @IsNumber({}, { each: true }) nationIds: number[]; // 用ID数组传递关联关系,而非实体实例 }
三、服务层逻辑校验
PlayerService的create方法需通过ID查询Nation实例,再关联到新创建的Player:
import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Player } from './player.entity'; import { CreatePlayerDto } from './dto/create-player.dto'; import { Nation } from '../nation/nation.entity'; @Injectable() export class PlayerService { constructor( @InjectRepository(Player) private playerRepo: Repository<Player>, @InjectRepository(Nation) private nationRepo: Repository<Nation>, ) {} async create(createPlayerDto: CreatePlayerDto) { // 1. 根据ID查询对应的Nation实例 const nations = await this.nationRepo.findByIds(createPlayerDto.nationIds); if (nations.length !== createPlayerDto.nationIds.length) { throw new NotFoundException('部分Nation不存在'); } // 2. 创建Player并关联Nation const player = this.playerRepo.create({ ...createPlayerDto, nations, // 直接关联查询到的Nation实例 }); // 3. 保存到数据库(TypeORM会自动维护中间表数据) return this.playerRepo.save(player); } }
检查点:需注入Nation的Repository,确保查询到的Nation数量和传入的ID数量一致,避免无效关联。
四、控制器逻辑校验
PlayerController的POST接口需接收DTO,并调用服务层方法:
import { Controller, Post, Body } from '@nestjs/common'; import { PlayerService } from './player.service'; import { CreatePlayerDto } from './dto/create-player.dto'; @Controller('players') export class PlayerController { constructor(private readonly playerService: PlayerService) {} @Post() async create(@Body() createPlayerDto: CreatePlayerDto) { return this.playerService.create(createPlayerDto); } }
五、Postman 测试步骤
提前创建Nation数据
- 请求方式:
POST - 请求URL:
http://localhost:3000/nations - 请求体(JSON):
重复此步骤创建多个Nation,记录返回的{ "name": "巴西" }id值。
- 请求方式:
创建关联Nation的Player
- 请求方式:
POST - 请求URL:
http://localhost:3000/players - 请求体(JSON):
{ "name": "内马尔", "nationIds": [1, 2] // 替换为实际创建的Nation ID }
- 请求方式:
验证关联结果
- 请求方式:
GET - 请求URL:
http://localhost:3000/players/1(替换为创建的Player ID) - 需确保返回结果包含
nations数组,且与传入的ID对应。
- 请求方式:
验证中间表数据
- 直接查询数据库的
player_nation表,确认存在player_id和nation_id的关联记录。
- 直接查询数据库的
内容的提问来源于stack exchange,提问作者bobby17jones
相关产品推荐
相关产品推荐

