Skip to content

第十章 多对多关系 ​

本章目标 ​

  1. 理解多对多关系为什么需要"中间表",以及中间表的结构。
  2. 能用 @ManyToMany + @JoinTable 建立双向多对多关系,并自定义中间表名和列名。
  3. 掌握两种增删关联的方式:改数组再 save、以及 relation().add()/remove() 直接操作中间表。

核心概念 ​

学生 ↔ 课程:一个学生可以选多门课,一门课有多个学生选。这种关系没法用一张外键列表达(一行学生记不下多个课程 id,一行课程也记不下多个学生 id),所以数据库引入第三张表——中间表(选课记录表),每行就是一对 (学生id, 课程id)。

TypeORM 里:

  • 两边实体都写 @ManyToMany;
  • 其中一边挂 @JoinTable(),表示"中间表由我负责",这边叫拥有方;
  • 中间表由 TypeORM 自动生成:两个外键列 + 联合主键,并且两个外键默认带 ON DELETE CASCADE(删文章/标签时自动清理中间表里的关联行)。

双向多对多标准写法 ​

  • post.ts
ts
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  ManyToMany,
  JoinTable,
} from "typeorm";
import { Tag } from "./Tag";

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToMany(() => Tag, (tag) => tag.posts)
  @JoinTable({
    name: "post_tag_relation",
    joinColumn: { name: "post_id", referencedColumnName: "id" },
    inverseJoinColumn: { name: "tag_id", referencedColumnName: "id" },
  })
  tags: Tag[];
}
  • Tag.ts
ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm";
import { Post } from "./Post";

@Entity()
export class Tag {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToMany(() => Post, (post) => post.tags)
  posts: Post[];
}

@JoinTable 自定义中间表 ​

不指定时 TypeORM 会自动起一个超长的表名(类似 post_tags_tag)。建议显式命名:

ts
  @ManyToMany(() => Tag, (tag) => tag.posts)
  @JoinTable({
    name: "post_tag_relation",
    joinColumn: { name: "post_id", referencedColumnName: "id" },
    inverseJoinColumn: { name: "tag_id", referencedColumnName: "id" },
  })
  tags: Tag[];
选项含义
name中间表名
joinColumn指向自己所在实体的外键列配置
inverseJoinColumn指向对方实体的外键列配置

多对对保存 只能用save 保存 关联关系 ​

ts
import { AppDataSource } from "./data-source";
import { Tag } from "./entities/Tag";
import { Post } from "./entities/Post";

AppDataSource.initialize()
  .then(async () => {
    const manage = AppDataSource.manager;

    // 先保存 Tag
    const tag1 = await manage.save(Tag, { name: "tag1" });
    const tag2 = await manage.save(Tag, { name: "tag2" });

    // 用 save() 保存 Post 并关联 tags,中间表才会写入数据
    await manage.save(Post, [
      { title: "post1", tags: [tag1] },
      { title: "post2", tags: [tag2] },
    ]);
  })
  .catch((error) => console.log(error));

增删关联的两种方式 ​

增加关联 ​

  • 找到post为3的数据以及他们的关联,如果没关联1,2,就关联1,2 关联了就不添加了
ts
import { AppDataSource } from "./data-source";
import { In } from "typeorm";
import { Tag } from "./entities/Tag";
import { Post } from "./entities/Post";

AppDataSource.initialize()
  .then(async () => {
    const manage = AppDataSource.manager;

    // 查询 id 为 3 的 Post,并加载已关联的 tags
    const post = await manage.findOne(Post, {
      where: { id: 3 },
      relations: { tags: true },
    });

    // 查询 id 为 1 和 2 的 Tag
    const tags = await manage.findBy(Tag, { id: In([1, 2]) });

    // 过滤掉已关联的 tag,只保留未关联的
    const existingTagIds = post.tags.map((t) => t.id);
    const newTags = tags.filter((t) => !existingTagIds.includes(t.id));

    if (newTags.length > 0) {
      post.tags = [...post.tags, ...newTags];
      await manage.save(Post, post);
    }
  })
  .catch((error) => console.log(error));

删除关联 ​

ts
import { AppDataSource } from "./data-source";
import { Post } from "./entities/Post";

AppDataSource.initialize()
  .then(async () => {
    const manage = AppDataSource.manager;

    // 查询 id 为 3 的 Post,并加载已关联的 tags
    const post = await manage.findOne(Post, {
      where: { id: 3 },
      relations: { tags: true },
    });

    // 过滤掉 id 为 1 和 2 的 tag,删除这些关联
    post.tags = post.tags.filter((t) => ![1, 2].includes(t.id));

    await manage.save(Post, post);
  })
  .catch((error) => console.log(error));

双向关联 ​

ts
import { AppDataSource } from "./data-source";
import { Post } from "./entities/Post";
import { Tag } from "./entities/Tag";
AppDataSource.initialize()
  .then(async () => {
    const manage = AppDataSource.manager;

    // Post -> Tag[]
    const post = await manage.find(Post, {
      where: { id: 1 },
      relations: { tags: true },
    });

    // Tag -> Post[](反向同样可用)
    const tag = await manage.find(Tag, {
      where: { id: 3 },
      relations: { posts: true },
    });

    console.log(tag);
    console.log(post);
  })
  .catch((error) => console.log(error));