多对多关系
什么是多对多关系
多对多关系是指A包含多个B实例,而B也包含多个A实例的关系。
我们以Question
(问题)和Category
(分类)实体为例。
一个问题可以有多个分类,而每个分类可以属于多个问题。
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
@ManyToMany(() => Category)
@JoinTable()
categories: Category[]
}
@JoinTable()
是在 @ManyToMany
关系中必须的。
您必须将 @JoinTable
放在关系的一方(拥有方)上。
这个示例会生成以下表格:
+-------------+--------------+----------------------------+
| category |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| title | varchar(255) | |
| text | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question_categories_category |
+-------------+--------------+----------------------------+
| questionId | int(11) | PRIMARY KEY FOREIGN KEY |
| categoryId | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
保存多对多关系
如果启用了 级联操作,您只需调用一次 save
方法即可保存这个关系。
const category1 = new Category()
category1.name = "animals"
await dataSource.manager.save(category1)
const category2 = new Category()
category2.name = "zoo"
await dataSource.manager.save(category2)
const question = new Question()
question.title = "dogs"
question.text = "who let the dogs out?"
question.categories = [category1, category2]
await dataSource.manager.save(question)
删除多对多关系
如果启用了 级联操作,您只需调用一次 save
方法即可删除这个关系。
要删除两条记录之间的多对多关系,请将其从相应字段中删除,并保存记录。
const question = await dataSource.getRepository(Question).findOne({
relations: {
categories: true,
},
where: { id: 1 }
})
question.categories = question.categories.filter((category) => {
return category.id !== categoryToRemove.id
})
await dataSource.manager.save(question)
这将只删除连接表中的记录。question
和 categoryToRemove
记录仍然存在。
级联软删除关系
以下示例展示了级联软删除的行为:
const category1 = new Category()
category1.name = "animals"
const category2 = new Category()
category2.name = "zoo"
const question = new Question()
question.categories = [category1, category2]
const newQuestion = await dataSource.manager.save(question)
await dataSource.manager.softRemove(newQuestion)
在此示例中,我们没有对 category1 和 category2 调用 save 或 softRemove,但是当关系的级联选项设置为 true 时,它们将被自动保存和软删除,如下所示:
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@ManyToMany(() => Category, (category) => category.questions, {
cascade: true,
})
@JoinTable()
categories: Category[]
}
加载多对多关系
要在加载问题时包含分类,请在 FindOptions
中指定关系:
const questionRepository = dataSource.getRepository(Question)
const questions = await questionRepository.find({
relations: {
categories: true,
},
})
或者使用 QueryBuilder
进行连接:
const questions = await dataSource
.getRepository(Question)
.createQueryBuilder("question")
.leftJoinAndSelect("question.categories", "category")
.getMany()
使用启用了 eager loading 的关系,您不必在查询命令中指定关系,因为它将始终自动加载。如果您使用 QueryBuilder,eager 关系将被禁用,您必须使用 leftJoinAndSelect
来加载关系。
双向关系
关系可以是单向的也可以是双向的。 单向关系是指只在一个方向上有关系装饰器的关系。 双向关系是指在关系的两个方向上都有装饰器的关系。
我们刚刚创建了一个单向关系。让我们把它改为双向关系:
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm"
import { Question } from "./Question"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Question, (question) => question.categories)
questions: Question[]
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
@ManyToMany(() => Category, (category) => category.questions)
@JoinTable()
categories: Category[]
}
我们刚刚使关系成为双向关系。请注意,反向关系没有 @JoinTable
。
@JoinTable
只能在关系的一侧使用。
双向关系允许您使用 QueryBuilder
从两个方向连接关系:
const categoriesWithQuestions = await dataSource
.getRepository(Category)
.createQueryBuilder("category")
.leftJoinAndSelect("category.questions", "question")
.getMany()
带有自定义属性的多对多关系
如果您需要在多对多关系中具有其他属性,则需要自己创建一个新的实体。
例如,如果您希望实体 Post
和 Category
具有带有额外的 order
列的多对多关系,那么您需要创建一个名为 PostToCategory
的实体,其中包含两个方向的 ManyToOne
关系,并且其中有自定义的列:
import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from "typeorm"
import { Post } from "./post"
import { Category } from "./category"
@Entity()
export class PostToCategory {
@PrimaryGeneratedColumn()
public postToCategoryId: number
@Column()
public postId: number
@Column()
public categoryId: number
@Column()
public order: number
@ManyToOne(() => Post, (post) => post.postToCategories)
public post: Post
@ManyToOne(() => Category, (category) => category.postToCategories)
public category: Category
}
此外,您还需要在 Post
和 Category
中添加以下关系:
// category.ts
...
@OneToMany(() => PostToCategory, postToCategory => postToCategory.category)
public postToCategories: PostToCategory[];
// post.ts
...
@OneToMany(() => PostToCategory, postToCategory => postToCategory.post)
public postToCategories: PostToCategory[];