You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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 测试步骤

  1. 提前创建Nation数据

    • 请求方式:POST
    • 请求URL:http://localhost:3000/nations
    • 请求体(JSON):
      { "name": "巴西" }
      
      重复此步骤创建多个Nation,记录返回的id值。
  2. 创建关联Nation的Player

    • 请求方式:POST
    • 请求URL:http://localhost:3000/players
    • 请求体(JSON):
      {
        "name": "内马尔",
        "nationIds": [1, 2] // 替换为实际创建的Nation ID
      }
      
  3. 验证关联结果

    • 请求方式:GET
    • 请求URL:http://localhost:3000/players/1(替换为创建的Player ID)
    • 需确保返回结果包含nations数组,且与传入的ID对应。
  4. 验证中间表数据

    • 直接查询数据库的player_nation表,确认存在player_id和nation_id的关联记录。

内容的提问来源于stack exchange,提问作者bobby17jones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 08:30:44