本文主要讲述如何用 typeorm 建表,建立一对一,一对多,多对多的关系,建立表的外连接。
以及在 typeorm 做查询操作的两种常用方式:Find 选项 和 QueryBuilder。
建表
typeorm 建表时,将 @Entity() 装饰的 class 映射为数据表,entity 中 @PrimaryColumn() 装饰的属性作为表的主键, @PrimaryGeneratedColumn() 表示自动生成主键, @Column() 装饰属性作为表的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
name: string;
@Column("text")
description: string;
@Column()
views: number;
@Column()
isPublished: boolean;
}
|
数据库中的列类型是根据你使用的属性类型推断的,例如: number 将被转换为 integer,string 将转换为 varchar,boolean 转换为 bool 等。下面我们从实际的例子出发探索如何用 typeorm 建一对一、一对多、多对多的关系。
一对一
用户 user 和用户档案 profile 是一对一关系,一个用户只有一份档案。
1
2
3
4
5
6
7
8
9
10
11
12
|
@Entity("users")
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@OneToOne((type) => ProfileEntity, (profile) => profile.user)
@JoinColumn()
profile: ProfileEntity;
}
|
注意
profile 是 ProfileEntity 类型的,在数据库中存储的类型却是 profile.id 的类型。
@OneToOne 中需要指明对方 entity 的类型,指明对方 entity 的外键。@JoinColumn 必须在且只在关系的一侧的外键上。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Entity("profiles")
export class ProfileEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
gender: string;
@Column()
photo: string;
@OneToOne((type) => UserEntity, (user) => user.profile)
user: UserEntity;
}
|
这将生成以下数据表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
+-------------+--------------+----------------------------+
| users |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| username | varchar(255) | |
| profileId | int(11) | FOREIGN KEY |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| profiles |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| gender | varchar(255) | |
| photo | varchar(255) | |
+-------------+--------------+----------------------------+
|
一对多
用户 user 与用户发布的文章 article 是一对多关系,一个用户可发布多篇文章。
1
2
3
4
5
6
7
8
9
10
11
|
@Entity("users")
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@OneToMany((type) => ArticleEntity, (article) => article.author)
articles: ArticleEntity[];
}
|
@OneToMany,@ManyToOne 中需要指明对方的 entity 类型,指明对方 entity 的外键。
1
2
3
4
5
6
7
8
9
10
11
|
@Entity("articles")
export class ArticleEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne((type) => UserEntity, (user) => user.articles)
author: UserEntity;
}
|
typeorm 在处理 “一对多”关系时将“一”的主键作为“多”的外键 (即 @ManyToOne 装饰的属性),建表时有最少的数据表操作代价,避免数据冗余,提高效率。这会生成以下表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
+-------------+--------------+----------------------------+
| articles |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| title | varchar(255) | |
| authorId | int(11) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| users |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| username | varchar(255) | |
+-------------+--------------+----------------------------+
|
多对多
用户 user 对文章 article 的喜欢 favorite 是多对多关系。一个用户可对多篇文章标记喜欢,一篇文章可被多个用户标记喜欢。
1
2
3
4
5
6
7
8
9
10
11
|
@Entity("users")
export class UserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@ManyToMany((type) => ArticleEntity, (article) => article.favoritedBy)
favorites: ArticleEntity[];
}
|
@OneToMany 中需要指明对方的 entity 类型,指明对方 entity 的外键。@JoinTable 必须在且只在关系的一侧的外键上。
1
2
3
4
5
6
7
8
9
10
11
12
|
@Entity("articles")
export class ArticleEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToMany((type) => UserEntity, (user) => user.favorites)
@JoinTable()
favoritedBy: UserEntity[];
}
|
typeorm 的处理方式是将多对多关系转化为两个一对多关系:
- 用户 user 与 喜欢 favorites 一对多。
- 文章 article 与被喜欢 favoritedBy 一对多。
多对多关系需要采用中间表的方式处理,这是为了避免笛卡尔积的出现。这会生成以下表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
+-------------+--------------+----------------------------+
| users |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| username | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| articles |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| title | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| articles_favorited_by_users |
+-------------+--------------+----------------------------+
| articlesId | int(11) | PRIMARY KEY FOREIGN KEY |
| usersId | int(11) | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
|
增删改查
创建好一对一,一对多,多对多的实体 entity 后,我们如何做增删改查呢?单个实体的 crud 可参考我的这一篇文章
。而关联后的实体对象会作为该实体对象的一个属性, 直接对属性进行操作即可。如下是文章被用户喜欢的实现:
1
2
3
4
5
6
|
async favoriteArticle(slug: string, user: UserEntity) {
const article = await this.articleRepo.findOne({ where: { slug }});
article.favoritedBy.push(user);
await article.save();
return article;
}
|
crud 操作中查询操作是我们最常遇到的,下面讲如何查询,typeorm 支持两种查询方式:Find 选项 和 QueryBuilder。
Find 选项
在 Nest.JS 中,对具体实体的管理(insert, update, delete, load 等)我们使用的是 Repository。对应的查找方法是:Repository.find(FindOptions)。
使用 find 查询只能获得一种类型的结果:entities。
find 选项的完整例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
userRepository.find({
select: ["username"],
relations: ["profile", "article","article.favoritedBy"],
where: {
username: "Timber",
},
order: {
id: "DESC"
},
skip: 5,
take: 10,
cache: true
});
|
直接使用 find 是不会查出关联的对象的,要查询的关联对象需要添加到 relations 数组中。
除了 relations 以外,其他选项等同于原生 sql 操作, order 等同于 order by, skip 等同于 offset, take 等同于 limit, cache 是查询缓存。细节请参考 Find 选项
。
这种查询有个局限就是只能查询到关联对象的整个实体或主键。而不能 select 关联实体的其他属性。因此更复杂的查询我们需要使用 QueryBuilder。
QueryBuilder
使用 QueryBuilder 查询可以获得两种类型的结果:entities 或原始数据。
要获取 entities,请使用 getOne 和 getMany。
要获取原始数据,请使用 getRawOne 和 getRawMany。
它能够很方便的帮我们构造出 sql 语句,addSelect() 可以获取关联对象上的其他属性。
1
2
3
4
5
6
7
8
9
|
if (query.author) {
const article = await getRepository(ArticleEntity)
.createQueryBuilder("article")
.select("article.id", "id")
.addSelect("favoritedBy.username", "name")
.leftJoin("article.favoritedBy", "favoritedBy")
.where("favoritedBy.username = :name", { name: query.author })
.getRawMany();
}
|
获取生成的 sql 语句可以在 getRawMany() 前获取 getSql() 或打印 printSql() 生成的 sql 语句。细节请参考 Query Builder
。
参阅资料