这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
问题
typeorm 中多对一的联表查询应该如何实现,我们通过一个问题来逐步的分析
表结构设计
为了说明的更清楚,我们直接使用 typeorm 官方文档的表结构例子稍作修改:
+-------------+--------------+----------------------------+
| photo |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| url | varchar(255) | |
| user_id | int(11) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
有 photo 和 user 两张表,其中一个 user 可以对应多条 photo(一个人可以照多张照片),photo 表中有字段 userId,表示这个照片是谁照的,对应 user 表中的 id。
实体设计
直接把表对应为 typeorm 中的实体,写法是这样的:
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";
@Entity()
export class Photo {
@PrimaryGeneratedColumn({
name: 'id'
})
id: number;
@Column({
name: 'url'
})
url: string;
@Column({
name: 'user_id'
})
userId: number;
}
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import {Photo} from "./Photo";
@Entity()
export class User {
@PrimaryGeneratedColumn({
name: 'id'
})
id: number;
@Column({
name: 'name'
})
name: string;
}
问题
问题是希望查出所有符合条件的 photo,并且关联查出它们对应的 user 信息,返回的结果就是这样的:
[{
id: 1,
url: "https://photo_url",
userId: 10000,
user: {
id: 10000,
name: '张三'
}
}, {
id: 2,
url: "https://photo_url",
userId: 10001,
user: {
id: 10001,
name: '李四'
}
},]
通过 SQL 如何解决
通过 SQL 解决,我们可以先只查询 user.name 试一试,会直接想到左连接的方式:
SELECT `photo`.`id` AS `photo_id`, `user`.`name` AS `user_name`
FROM `photo` `photo`
LEFT JOIN `user` `user`
ON `user`.`id` = photo.user_id
通过 typeorm 实现
首先要注意,我们实际查询出的结果,是需要把 user 的数据塞进 photo 里的,typeorm 中每个查询结果都必须映射到对应的 Model 中,于是我们前面定义的实体结构还需要增加属性
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";
@Entity()
export class Photo {
@PrimaryGeneratedColumn({
name: 'id'
})
id: number;
@Column({
name: 'url'
})
url: string;
@Column({
name: 'user_id'
})
userId: number;
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
public user: User;
}
我们可以看到,新增的部分是
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
public user: User;
下面我们来分别解释下它们的含义
ManyToOne
官方文档写的太简单了,但是也可以参考下。
orkhan.gitbook.io/typeorm/doc…
官方文档翻译:typeorm.bootcss.com/many-to-one…
这个 stackoverflow 的问题解释了一些:stackoverflow.com/questions/5…
简单来说 @ManyToOne
用来定义一个多对一的关系字段,比如这里的多个 photo 对应一个 user,它的装饰器需要两个函数,第一个函数参数可以不关心,返回值是相关的实体,第二个函数返回相关实体的“外键”属性(另一个实体,比如 user,没有定义 @OneToMany
字段的时候可以省略)
被他装饰的属性是 public user: User;
他的类型就是 User
查询方式修改
await getRepository(Photo)
.createQueryBuilder('photo')
.leftJoinAndSelect('photo.user', 'user') // 注意这里
.select('photo')
.addSelect('user') // 和这里
.getMany();
因为被 ManyToOne 装饰的属性 user 的类型是User
,所以 photo 表可以直接连接 photo.user
,但是连接默认的规则,也就是 ON
的条件是 user
.id
=photo
.userId
,前面半句是 photo 表的主键列 id 就不说了,后面半句photo
.userId
,是刚刚声明的属性 user
默认拼接上 Id
但是我们的数据表里并没有 userId
这一外键列,于是需要定义成自己的字段名,于是就有了 JoinColumn
JoinColumn
JoinColumn 的作用一个是定义了哪一个表是包含外键的,另外可以自定义连接列和引用列的名称
当我们设置 @JoinColumn
时,它会自动在数据库中创建一个名为 propertyName + referencedColumnName
的列。例如:
@ManyToOne(() => User)
@JoinColumn() // this decorator is optional for @ManyToOne, but required for @OneToOne
public user: User;
这个代码将在数据库中创建一个 userId 列。如果要在数据库中更改此名称,可以指定自定义连接列名称,就像我们例子中做的一样
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
public user: User;