Skip to content

第八章 一对多与多对一关系

本章目标

  1. 理解一对多关系中外键永远在"多方"表上。
  2. 能用 @ManyToOne + @OneToMany 建立双向的一对多关系。
  3. 明白 @OneToMany 只是反向映射、数据库里不存在对应字段,以及只写单向 @OneToMany 的后果。
  4. 掌握一对多的保存顺序与双向查询。

核心概念

一个作者 ↔ 多篇文章:一个作者能写很多篇文章,但每篇文章(在本例中)只属于一个作者。

数据库的表达方式是:在"多方"表(book)上加一个外键列 authorId,指向"一方"表(author)的主键。注意:

  • 多方表上有外键列 → 多方是 拥有方
  • 一方表上什么列都没有——"一个作者有哪些书"是靠查询 book.authorId = 作者id 反推出来的。

对应到 TypeORM:

装饰器写在哪数据库里有什么
@ManyToOne多方实体(Book)book 表上真实的 authorId 外键列
@OneToMany一方实体(Author)什么都没有,纯反向映射,方便从作者查书

一对多关系不需要 @JoinColumn——@ManyToOne 本身就隐含"外键在我这边"。

标准写法:多方 @ManyToOne + 一方 @OneToMany

Author.ts

ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Book } from "./Book";

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

  @Column()
  name: string;

  // 反向映射:第二个参数指明对方实体上回指自己的属性
  @OneToMany(() => Book, (book) => book.author)
  books: Book[];
}

Book.ts

ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { Author } from "./Author";

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

  @Column()
  title: string;

  @ManyToOne(() => Author, (author) => author.books)
  author: Author;
}

建表结果:book 表多出 authorId 外键列;author 表没有任何变化。

  • 写了ManyToOne,代表这个表上有外键

2. 为什么不建议只写单向 @OneToMany

@OneToMany 自己是"虚"的,它必须靠对方的 @ManyToOne 才知道外键在哪。如果一方写了 @OneToMany 而多方不写 @ManyToOne,TypeORM 1.1.1 的实际行为是(演示文件里有完整实验):

  • 多方表上不会生成外键列——关系根本没有落到数据库;
  • 保存多方数据时,赋上去的作者对象被静默忽略
  • relations: { books: true } 查询时直接报错:Cannot read properties of undefined (reading 'joinColumns')

反过来,只写 @ManyToOne(不写 @OneToMany)是完全合法的——外键照样建好,只是不能从一方方便地反查。结论:@ManyToOne 是必须的,@OneToMany 是可选的便利反向映射。

保存

记住一点 有外键的表最后在存,所以先存一方,再存多方

ts
import { AppDataSource } from "./data-source";
import { Author } from "./entities/Author";
import { Book } from "./entities/Book";
AppDataSource.initialize()
  .then(async () => {
    const manage = AppDataSource.manager;
    const author = new Author();
    author.name = "老舍";
    await manage.insert(Author, author); // 先存作者,拿到 id

    const book = new Book();
    book.title = "骆驼祥子";
    book.author = author; // 多方把作者对象赋给关系属性

    const book2 = new Book();
    book2.title = "骆驼祥子2";
    book2.author = author; // 多方把作者对象赋给关系属性

    const book_all = [book, book2];
    await manage.insert(Book, book_all);
  })
  .catch((error) => console.log(error));

分开查询

ts
import { AppDataSource } from "./data-source";
import { Profile } from "./entities/Profile";
import { Author } from "./entities/Author";
import { Book } from "./entities/Book";
AppDataSource.initialize()
  .then(async () => {
    const manage = AppDataSource.manager;
    const result = await manage.find(Author, { relations: { books: true } });
    console.log("---通过人物查询到书---");
    console.log(result);
    console.log(result[0].books);

    const result2 = await manage.find(Book, { relations: { author: true } });
    console.log("---通过书查询到人物---");
    console.log(result2);
    console.log(result2[0].author);
  })
  .catch((error) => console.log(error));