前言
大家好!我是 嘟老板。在服务端项目中,各个业务实体基本不是孤立存在,彼此之间存在多种关系,比如 用户 和 用户档案,每个用户仅能维护一个用户档案,而每个档案也专属于一个用户,算是比较典型的 一对一关系。那 TypeORM
框架是如何处理实体间 一对一关系 的呢?又该如何实现呢?请带着问题,随我走进今天的内容。
阅读本文您将收获:
- 了解
TypeORM
实体类如何定义 一对一关系。- 了解相关装饰器及用法。
- 了解实体数据处理方法,如查询、新增、删除等。
关系种类
- 一对一(
One-to-One OTO
) - 一对多(
One-to-Many OTM
) / 多对一(Many-to-One MTO
) - 多对多(
Many-to-Many MTM
)
本文重点讨论 一对一 关系。
相关文章:
OTO 关系介绍
一对一关系 中,在两个存在关系的实体间,每个实体的实例仅能与另一个实体的 一个 实例相关联,反之亦然。
一对一关系 在数据库层面的实现涉及以下两部分:
- 实体表:每个实体类型都有其自己的表,用于存储各自实体的数据。例如,存储用户的用户表和存储用户档案的档案表。
- 关联字段 & 外键:通常把一个实体的主键用作另一个实体的外键。例如,用户表中的 档案ID 通常是档案表的 主键 ID。
以 用户 和 用户档案 实体为例,需要分别设计 用户表 和 档案表,其中用户表除基础字段外,可额外设计 档案 ID 字段,用以关联用户档案。
装饰器介绍
@OneToOne
作用
一对一关系 专用装饰器,用来定义两个实体间的 一对一关系。
例如,用户实体类 User
:
import { Entity, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm'
import { Profile } from './profile.entity'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
// 其他属性字段...
@OneToOne(() => Profile)
@JoinColumn()
profile: Profile;
}
其中 @OneToOne
装饰器作用于 profile
字段,参数 () => Profile
表明 User
实体与 Profile
实体存在一对一关联关系,且 profile
字段存储关联的 Profile
数据。
参数
基于不同的应用场景,@OneToOne
装饰器可接受以下参数:
-
typeFunctionOrTarget
:函数类型,返回与当前实体相关联的实体类的构造函数,例如() => Profile
表示profile
属性关联的是Profile
实体。 -
inverseSide
:函数类型,双向关系 时使用,用于定义关系的实体关联属性,双向关系 时两个实体的关联列都要用@OneToOne
装饰器。
如 User
和 Profile
双向关联,需要将 User
类的 profile
字段调整如下:
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
// 其他属性字段...
@OneToOne(() => Profile, (profile) => profile.user)
@JoinColumn()
profile: Profile;
}
其中 @OneToOne
第二个参数 (profile) => profile.user
表示 Profile
实体的 user
属性存储关联的 User
实体数据。
Profile
类需要定义 user
字段,并使用 @OneToOne
装饰器修饰。
import { Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
import { User } from './user.entity'
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number
// 其他属性字段...
@OneToOne(() => User, (user) => user.profile)
user: User
}
-
options
:关系配置项,包含以下常用配置:cascade
:若设置为true
,表示允许关联的实体数据同步 保存/更新/删除 到数据库表;也可设置为具体的操作数组,如['insert']
,表示进行指定操作时,才同步执行。nullable
:设置关联列的值是否 允许为空。lazy
:设置关联实体数据是否 延迟加载。eager
:设置是否 自动加载 关联实体数据。onDelete
:设置 删除 时的级联操作,比如删除用户表数据时,用户档案表是否一同删除。onUpdate
:设置 更新 时的级联操作,比如更新用户表数据时,用户档案表是否一同更新。- ...
@OneToOne(() => Profile, profile => profile.user, {
cascade: true,
eager: true,
onDelete: "CASCADE"
})
@JoinColumn()
profile: Profile;
以上代码表示启用级联查询,自动加载,且在删除用户数据时,同步删除关联的档案数据。
@JoinColumn
用途
该装饰器主要作用有以下两点:
- 定义两个实体间的关联列,即 外键。
- 允许我们自定义 外键列表字段名称 及其引用的另一个实体的 表字段。
@JoinColumn
仅在包含的外键的实例类中使用,比如 User
类包含外键列 profile
,则只需加在 profile
属性上即可,不需要在 Profile
类中使用。
参数
@JoinColumn
装饰器可接收如下三个参数:
name
:外键列的表字段名称。referencedColumnName
:外键列指向的实体表字段名称。foreignKeyConstraintName
:外键约束名称。
使用方式如下:
@OneToOne(() => Profile, profile => profile.user)
@JoinColumn({ name: 'profile_id', referencedColumnName: 'id' })
profile: Profile;
@JoinColumn
指明用户表 user
的外键列是 profile_id
,指向档案表 profile
的 id
列。
数据操作
保存
保存操作包括数据 新增 和 更新,可借助 EntityManager
save
api 实现
若启用了 级联配置,即 @OneToOne
装饰器中设置 cascades 为 true
,则只需调用一次 save
即可。
例如 User
类 profile
属性定义如下:
@OneToOne(() => Profile, profile => profile.user, {
cascade: true
})
@JoinColumn()
profile: Profile;
通过以下方式即可同时保存 User
和 Profile
数据。
const profile = new Profile()
profile.gender = "M"
profile.tel = "13912341234"
const user = new User()
user.name = "dulaoban"
user.profile = profile
await dataSource.manager.save(user)
查询
查询类 api
以 Repository.find
为例,参数中指定 relations
选项,设置 profile
为 true
,表示要查询关联实体数据 profile
。
const users = await dataSource.getRepository(User).find({
relations: {
profile: true,
},
})
自动加载 - eager
上文提到 @OneToOne
装饰器可接收三个参数,其中第三个参数是个配置对象,eager
就是配置项之一,表示自动加载关联的实体数据。
我们将 User
profile
属性定义如下:
@OneToOne(() => Profile, profile => profile.user, {
eager: true
})
@JoinColumn()
profile: Profile;
然后调用查询类 api
,如 Repository.find
,无需指定 relations
配置项,即可自动查询关联实体数据。
const users = await dataSource.getRepository(User).find()
QueryBuilder
借助 leftJoinAndSelect
api
添加并执行左连接查询,获取关联实体数据。
const users = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.getMany()
@OneToOne
的 eager
配置对于 QueryBuilder
方式无效,因此即便设置 eager
为 true
,也不会自动加载关联实体数据,必须调用 leftJoinAndSelect
。
多说两句
简单介绍下 leftJoinAndSelect
api
,后续会单独写篇 QueryBuilder
的文章:
从上面的例子可以看出,leftJoinAndSelect
接收了两个参数:
- 第一个参数表示要加载的关系,比如
user
关联的profile
。 - 第二个参数指定关系表的别名,用以简化
SQL
查询中的引用,如指定档案表别名为profile
,这个别名可在后续的任何位置引用,如指定查询条件where
。
再次搬出查询 User
的例子:
const users = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.getMany()
以上代码对应的 SQL
语句如下:
SELECT user.*, profile.* FROM user user
LEFT JOIN profile profile ON user.profile_id = profile.id
结语
本文重点介绍了 TypeORM
对于业务实体一对一关系的支持及相关用法介绍,包括装饰器(@OneToOne
、@JoinColumn
)、数据操作(保存、查询)等,旨在帮助同学们加深对于业务实体一对一关系的应用理解。希望对您有所帮助。ZimuAdmin 服务端将有对 TypeORM
的深度应用,欢迎 star。
如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。
技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。