关系
关系
关系可以帮助你轻松地与相关实体合作。有几种类型的关系:
你可以为关系指定几个选项:
eager: boolean
- 如果设置为 true,则在此实体上使用find *
或QueryBuilder
时,将始终使用主实体加载关系cascade: boolean
- 如果设置为 true,则将插入相关对象并在数据库中更新。onDelete: "RESTRICT"|"CASCADE"|"SET NULL"
- 指定删除引用对象时外键的行为方式primary: boolean
- 指示此关系的列是否为主列。nullable: boolean
-指示此关系的列是否可为空。默认情况下是可空。
一对一
一对一是一种 A 只包含一个 B 实例,而 B 只包含一个 A 实例的关系。我们以 User 和 Profile 实体为例。用户只能拥有一个配置文件,并且一个配置文件仅由一个用户拥有。
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
gender: string;
@Column()
photo: string;
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(type => Profile)
@JoinColumn()
profile: Profile;
}
这里我们将 @OneToOne 添加到 profile 并将目标关系类型指定为 Profile。我们还添加了 @JoinColumn,这是必选项并且只能在关系的一侧设置。你设置 @JoinColumn 的哪一方,哪一方的表将包含一个 “relation id” 和目标实体表的外键。此示例将生成以下表:
+-------------+--------------+----------------------------+
| profile |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| gender | varchar(255) | |
| photo | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
| profileId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
同样,@JoinColumn 必须仅设置在关系的一侧且必须在数据库表中具有外键的一侧。该例子展示如何保存这样的关系:
const profile = new Profile();
profile.gender = "male";
profile.photo = "me.jpg";
await connection.manager.save(profile);
const user = new User();
user.name = "Joe Smith";
user.profile = profile;
await connection.manager.save(user);
启用级联后,只需一次save
调用即可保存此关系。要加载带有配置文件的用户,必须在FindOptions
中指定关系:
const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["profile"] });
或者使用 QueryBuilder
:
const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.profile", "profile")
.getMany();
通过在关系上启用预先加载,你不必指定关系或手动加入,它将始终自动加载。
双向关系
关系可以是单向的和双向的。单向是仅在一侧与关系装饰器的关系。双向是与关系两侧的装饰者的关系。我们刚刚创建了一个单向关系。让我们将它改为双向:
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
gender: string;
@Column()
photo: string;
@OneToOne(
type => User,
user => user.profile
) // 将另一面指定为第二个参数
user: User;
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(
type => Profile,
profile => profile.user
) // 指定另一面作为第二个参数
@JoinColumn()
profile: Profile;
}
我们只是创建了双向关系。注意,反向关系没有@JoinColumn。@JoinColumn 必须只在关系的一侧且拥有外键的表上。双向关系允许你使用QueryBuilder
从双方加入关系:
const profiles = await connection
.getRepository(Profile)
.createQueryBuilder("profile")
.leftJoinAndSelect("profile.user", "user")
.getMany();
多对一/一对多的关系
多对一/一对多是指 A 包含多个 B 实例的关系,但 B 只包含一个 A 实例。让我们以 User 和 Photo 实体为例。User 可以拥有多张 photos,但每张 photo 仅由一位 user 拥有。
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@ManyToOne(
type => User,
user => user.photos
)
user: User;
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(
type => Photo,
photo => photo.user
)
photos: Photo[];
}
这里我们将 @ManyToOne 添加到 photos 属性中,并将目标关系类型指定为 Photo。你也可以在 @ManyToOne/@OneToMany 关系中省略 @JoinColumn。没有 @ManyToOne,@OneToMany 就不可能存在。如果你想使用 @OneToMany,则需要 @ManyToOne。在你设置@ManyToOne 的地方,相关实体将有"关联 id"和外键。
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| url | varchar(255) | |
| userId | int(11) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
如何保存这种关系:
const photo1 = new Photo();
photo1.url = "me.jpg";
await connection.manager.save(photo1);
const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
await connection.manager.save(photo2);
const user = new User();
user.name = "John";
user.photos = [photo1, photo2];
await connection.manager.save(user);
或者你可以选择:
const user = new User();
user.name = "Leo";
await connection.manager.save(user);
const photo1 = new Photo();
photo1.url = "me.jpg";
photo1.user = user;
await connection.manager.save(photo1);
const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
photo2.user = user;
await connection.manager.save(photo2);
启用级联后,只需一次 save
调用即可保存此关系。要在内部加载带有 photos 的 user,必须在FindOptions
中指定关系:
const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["photos"] });
// or from inverse side
const photoRepository = connection.getRepository(Photo);
const photos = await photoRepository.find({ relations: ["user"] });
或者使用 QueryBuilder
:
const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.getMany();
// or from inverse side
const photos = await connection
.getRepository(Photo)
.createQueryBuilder("photo")
.leftJoinAndSelect("photo.user", "user")
.getMany();
通过在关系上启用预先加载,你不必指定关系或手动加入,它将始终自动加载。
多对多的关系
多对多是一种 A 包含多个 B 实例,而 B 包含多个 A 实例的关系。我们以 Question 和 Category 实体为例。Question 可以有多个 categories, 每个 category 可以有多个 questions。
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
@ManyToMany(type => 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) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question_categories_category |
+-------------+--------------+----------------------------+
| questionId | int(11) | PRIMARY KEY FOREIGN KEY |
| categoryId | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
如何保存这种关系:
const category1 = new Category();
category1.name = "animals";
await connection.manager.save(category1);
const category2 = new Category();
category2.name = "zoo";
await connection.manager.save(category2);
const question = new Question();
question.categories = [category1, category2];
await connection.manager.save(question);
启用级联后,只需一次save
调用即可保存此关系。要在 categories 里面加载 question,你必须在FindOptions
中指定关系:
const questionRepository = connection.getRepository(Question);
const questions = await questionRepository.find({ relations: ["categories"] });
或者使用QueryBuilder
const questions = await connection
.getRepository(Question)
.createQueryBuilder("question")
.leftJoinAndSelect("question.categories", "category")
.getMany();
通过在关系上启用预先加载,你不必指定关系或手动加入,它将始终自动加载。
双向关系
关系可以是单向的和双向的。单向是仅在一侧与关系装饰器的关系。双向是与关系两侧的装饰者的关系。我们刚刚创建了一个单向关系。让我们改为双向:
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(
type => Question,
question => question.categories
)
questions: Question[];
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
@ManyToMany(
type => Category,
category => category.questions
)
@JoinTable()
categories: Category[];
}
我们只是创建了双向关系。注意,反向关系没有 @JoinTable
,@JoinTable
必须只在关系的一边。双向关系允许您使用 QueryBuilder
从双方加入关系:
const categoriesWithQuestions = await connection
.getRepository(Category)
.createQueryBuilder("category")
.leftJoinAndSelect("category.questions", "question")
.getMany();
Eager 和 Lazy 关系
Eager 关系
每次从数据库加载实体时,都会自动加载 Eager 关系。例如:
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(
type => Question,
question => question.categories
)
questions: Question[];
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
@ManyToMany(
type => Category,
category => category.questions,
{
eager: true
}
)
@JoinTable()
categories: Category[];
}
现在当你加载 questions 时,不需要加入或指定要加载的关系。它们将自动加载:
const questionRepository = connection.getRepository(Question);
// questions 将加载其类别 categories
const questions = await questionRepository.find();
Eager 关系只有在使用find *
方法时才有效。如果你使用QueryBuilder
,则禁用 eager 关系,并且必须使用leftJoinAndSelect
来加载。Eager 的关系只能用于关系的一方,在关系的两边使用eager:true
是不允许的。
Lazy 关系
当你访问的时候会加载 Lazy 关系中的实体。这种关系必须有Promise
作为类型,并且将值存储在一个 promise 中,当你加载它们时,也会返回 promise。例如:
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(
type => Question,
question => question.categories
)
questions: Promise<Question[]>;
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
@ManyToMany(
type => Category,
category => category.questions
)
@JoinTable()
categories: Promise<Category[]>;
}
categories
是一个 Promise. 这意味着它是 lazy 的,它只能存储一个带有值的 promise。
例如:
保存这种关系:
const category1 = new Category();
category1.name = "animals";
await connection.manager.save(category1);
const category2 = new Category();
category2.name = "zoo";
await connection.manager.save(category2);
const question = new Question();
question.categories = Promise.resolve([category1, category2]);
await connection.manager.save(question);
如何在 Lazy 关系中加载对象:
const question = await connection.getRepository(Question).findOne(1);
const categories = await question.categories;
// you'll have all question's categories inside "categories" variable now
注意:如果你来自其他语言(Java,PHP 等)并且习惯于在任何地方使用 lazy 关系,请小心使用。这些语言不是异步的,延迟加载是以不同的方式实现的,这就是为什么不能使用 promises 的原因。在 JavaScript 和 Node.JS 中,如果你想拥有延迟加载的关系,你必须使用 promises。但是这是非标准技术,而且在 TypeORM 中被认为是实验性的。