typeorm 多对一联表查询方式(ManyToOne,JoinColumn,relation)

4,884 阅读3分钟

这是我参与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;