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

NestJS+TypeORM多对多关系如何单次请求新增Item并关联多个已有分类

问题背景

我有两张表,分别为Items和Categories,二者为多对多关系,即一个Item可归属多个分类,一个分类下也可包含多个Item。

示意图:
多对多关系示意图

当前使用的技术栈为NestJS搭配TypeORM。

遇到的问题

需要在单次请求中完成Item入库,同时为其绑定多个所属分类。分类数据已提前完成数据库初始化且数量固定,无需同步新增分类数据。

现有临时实现

当前方案是先通过createItemDto中传入的categoryID查询到对应的分类实体,再在createItem方法中将新增的Item实例添加到分类实体的items数组中完成关联,现有代码如下:

items.service.ts中的CreateItem方法

async createItem(
  createItemDto: CreateItemDto,
  user: User,
  category: Category,
): Promise<Item> {
  const newItem = await this.itemsRepository.save({
    name: createItemDto.name,
    description: createItemDto.description,
    price: createItemDto.price,
    delivery: createItemDto.delivery,
    cancellation: createItemDto.cancellation,
    rating: createItemDto.rating,
    imageUrl: createItemDto.imageUrl,
  });

  user.items = [...user.items, newItem];
  await user.save();

  category.items = [...category.items, newItem];
  await category.save();

  return newItem;
}

items.controller.ts

@Post('/createitem')
async createItem(@Body() createItemDto: CreateItemDto): Promise<Item> {
  const user = await this.authService.getUserById(createItemDto.userId);
  const category = await this.categoriesService.getCategoryById(
    createItemDto.categoryId,
  );
  return this.itemsService.createItem(createItemDto, user, category);
}

Item实体

import { Category } from './category.entity';
import { Rent } from './rent.entity';
import { User } from './user.entity';
import {
  BaseEntity,
  Column,
  Entity,
  JoinTable,
  ManyToMany,
  ManyToOne,
  OneToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';

@Entity('item')
export class Item extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  description: string;

  @Column()
  price: number;

  @Column()
  delivery: boolean;

  @Column()
  cancellation: boolean;

  @Column({ nullable: true })
  rating: number;

  @Column()
  imageUrl: string;

  @ManyToOne(() => User, (user) => user.items, {
    onDelete: 'CASCADE',
  })
  user: User;

  @OneToOne(() => Rent, (rent) => rent.item)
  rent: Rent;

  @ManyToMany(() => Category, (category) => category.items)
  @JoinTable()
  categories: Category[];
}

Category实体

import { Item } from './item.entity';
import {
  BaseEntity,
  Column,
  Entity,
  JoinTable,
  ManyToMany,
  PrimaryGeneratedColumn,
} from 'typeorm';

@Entity()
export class Category extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  category: string;

  @ManyToMany(() => Item, (item) => item.categories)
  items: Item[];
}

Categories服务

import { Category } from './../entities/category.entity';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CategoriesRepository } from './categories.repository';

@Injectable()
export class CategoriesService {
  constructor(
    @InjectRepository(CategoriesRepository)
    private categoriesRepository: CategoriesRepository,
  ) {}

  async getCategoryById(id: number): Promise<Category> {
    return await this.categoriesRepository.findOne(id, {
      relations: ['items'],
    });
  }
}

解决方案

可直接利用TypeORM的关联自动维护能力,单次操作完成Item入库和多分类绑定,无需手动更新关联实体的属性,性能更高,具体修改如下:

步骤1:调整CreateItemDto,支持多分类ID传入

将Dto中原本的单值categoryId字段修改为数组类型categoryIds: number[],适配多分类绑定需求。

步骤2:修改Controller逻辑,批量查询分类

@Post('/createitem')
async createItem(@Body() createItemDto: CreateItemDto): Promise<Item> {
  const user = await this.authService.getUserById(createItemDto.userId);
  // 批量查询所有需要关联的分类实体
  const categories = await Promise.all(
    createItemDto.categoryIds.map(id => this.categoriesService.getCategoryById(id))
  );
  return this.itemsService.createItem(createItemDto, user, categories);
}

步骤3:修改Service的createItem方法,直接关联数据

async createItem(
  createItemDto: CreateItemDto,
  user: User,
  categories: Category[],
): Promise<Item> {
  // 保存Item时直接赋值关联属性,TypeORM会自动维护中间表数据
  const newItem = await this.itemsRepository.save({
    name: createItemDto.name,
    description: createItemDto.description,
    price: createItemDto.price,
    delivery: createItemDto.delivery,
    cancellation: createItemDto.cancellation,
    rating: createItemDto.rating,
    imageUrl: createItemDto.imageUrl,
    user: user,
    categories: categories
  });
  return newItem;
}

可选优化

分类查询不需要携带items关联字段,仅需要分类主键即可完成关联,可修改getCategoryById方法减少不必要的查询开销:

async getCategoryById(id: number): Promise<Category> {
  return await this.categoriesRepository.findOne(id);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 14:36:06