第八章 一对一关系
本章目标
- 理解"拥有方 / 被拥有方"的概念,知道
@JoinColumn该写在哪一边。 - 能用
@OneToOne建立单向和双向一对一关系。 - 掌握一对一关系的保存顺序。
- 会用
relations选项和 QueryBuilder 的leftJoinAndSelect查询关联数据。
核心概念
一个人 ↔ 一张身份证:一个人只有一张有效身份证,一张身份证也只属于一个人,这就是一对一关系。
在数据库里,一对一关系的实现方式是:在其中一张表上加一个"外键列 + 唯一约束",指向另一张表的主键。
- 放了外键列的那张表,对应的实体叫 拥有方(owning side);
- 没放外键、被指向的那张表,对应的实体叫 被拥有方(inverse side);
- TypeORM 用
@JoinColumn()标记"外键列放在我这边"。@JoinColumn必须且只能写在拥有方。
最小的一对一关系(单向)
ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from "typeorm";
@Entity()
class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
avatar: string;
@Column()
bio: string;
}
@Entity()
class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(() => Profile) // 目标实体:Profile
@JoinColumn() // 外键列建在 user 表上
profile: Profile;
}建表后,user 表会多出一个 profileId 列,并且带唯一约束 + 外键(唯一约束正是"一对一"的保障——同一个 profileId 不能被两个 user 引用)。
双向一对一
- 单向关系只能从 User 查到 Profile。想从 Profile 反查 User,要在 Profile 上补一个反向映射:
ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from "typeorm";
import { User } from "./User";
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
avatar: string;
@Column()
bio: string;
@OneToOne(() => User, (user) => user.profile) // 反向映射到 User.profile
user: User;
}使用
- 因为外键在user表,所以先插入profile表,等profile表中有id,再插入user表
ts
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
import { Profile } from "./entities/Profile";
AppDataSource.initialize()
.then(async () => {
const profile = new Profile();
profile.avatar = "https://www.baidu.com";
profile.bio = "a bio";
await AppDataSource.manager.insert(Profile, profile);
const user = new User();
user.name = "yl840414";
user.profile = profile;
await AppDataSource.manager.insert(User, user);
})
.catch((error) => console.log(error));每次都存两次很麻烦?可以用
cascade: true让保存 User 时自动连带保存 Profile,见级联操作。
查询的两种方式
1. 用 relations 选项查询关联数据
ts
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
AppDataSource.initialize()
.then(async () => {
const result = await AppDataSource.manager.find(User, {
relations: {
profile: true,
},
});
console.log(result);
})
.catch((error) => console.log(error));2. 使用 QueryBuilder 的 leftJoinAndSelect 查询关联数据
ts
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
AppDataSource.initialize()
.then(async () => {
const userRepo = AppDataSource.getRepository(User);
const user = await userRepo
.createQueryBuilder("user")
.leftJoinAndSelect("user.profile", "profile")
.where("user.id = :id", { id: 1 })
.getOne();
console.log(user);
})
.catch((error) => console.log(error));双向查询
ts
import { AppDataSource } from "./data-source";
import { Profile } from "./entities/Profile";
AppDataSource.initialize()
.then(async () => {
const result = await AppDataSource.manager.find(Profile, {
relations: {
user: true,
},
});
console.log(result);
})
.catch((error) => console.log(error));