第八章 一对多与多对一关系
本章目标
- 理解一对多关系中外键永远在"多方"表上。
- 能用
@ManyToOne+@OneToMany建立双向的一对多关系。 - 明白
@OneToMany只是反向映射、数据库里不存在对应字段,以及只写单向@OneToMany的后果。 - 掌握一对多的保存顺序与双向查询。
核心概念
一个作者 ↔ 多篇文章:一个作者能写很多篇文章,但每篇文章(在本例中)只属于一个作者。
数据库的表达方式是:在"多方"表(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));