TypeORM 一对多关联篇:用最直观的例子讲清 OneToMany / ManyToOne

96 阅读3分钟

这节继续学习 TypeORM 的一对多关系映射和对应的 CRUD。

前置工作

  1. 创建 typeorm 项目
npx typeorm@latest init --name typeorm_one_to_more_relation_mapping --database mysql
  1. 安装 mysql2 驱动包
npm i mysql2
  1. 修改 data-source.ts 配置
import "reflect-metadata"
import {DataSource} from "typeorm"
import {User} from "./entity/User"export const AppDataSource = new DataSource({
  type: "mysql",
  host: "localhost",
  port: 3306,
  username: "root",
  password: "admin",
  database: "typeorm_test",
  synchronize: true,
  logging: true,
  entities: [User],
  migrations: [],
  subscribers: [],
  poolSize: 10,
  connectorPackage: 'mysql2',
  extra: {
    authPlugin: 'sha256_password'
  }
})

具体参数配置解释可见之前的文章: TypeORM 基础篇:项目初始化与增删改查全流程

创建部门 department 和员工 employee 两个实体

import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"@Entity()
export class Department {
​
  @PrimaryGeneratedColumn()
  id: number
​
  @Column({
    length: 100
  })
  name: string
​
}
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"@Entity()
export class Employee {
​
  @PrimaryGeneratedColumn()
  id: number
​
  @Column({
    length: 100
  })
  name: string
​
}

把这两个实体放到 data-source 的 entities 属性中:

image-20251021075738995.png

删除 User 实体以及 index.ts 中相关代码。

import { AppDataSource } from "./data-source"AppDataSource.initialize().then(async () => {
​
  console.log('...')
​
}).catch(error => console.log(error))

添加一对多映射

1. 在 “多” 的一方 通过 @ManyToOne 装饰器 添加。

import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm"
import {Department} from "./Department";
​
@Entity()
export class Employee {
​
  @PrimaryGeneratedColumn()
  id: number
​
  @Column({
    length: 100
  })
  name: string
​
  @ManyToOne(() => Department, {
    cascade: true // 级联保存
  })
  department: Department
​
}

在 index.ts 中增加一些数据:

import { AppDataSource } from "./data-source"
import {Department} from "./entity/Department";
import {Employee} from "./entity/Employee";
​
AppDataSource.initialize().then(async () => {
​
  const dept1 = new Department()
  dept1.name = '产品部'
​
  const em1 = new Employee()
  em1.name = '三十1'
  em1.department = dept1
​
  const em2 = new Employee()
  em2.name = '三十2'
  em2.department = dept1
​
  const em3 = new Employee()
  em3.name = '三十3'
  em3.department = dept1
​
  const em4 = new Employee()
  em4.name = '三十4'
  em4.department = dept1
​
  // await AppDataSource.manager.save(dept1)
  // 因为设置了 cascade: true,所以这里只需要保存员工即可,部门会自动保存
  await AppDataSource.manager.save([em1, em2, em3, em4])
​
​
}).catch(error => console.log(error))

执行 npm run start 即可创建对应的表和关系。

image-20251021082328565.png

2. 在 “一”的一方 通过 @OneToMany 装饰器添加

先将 Employee 实体中的 cascade: true 去掉,避免两个表都级联保存导致无限保存

import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm"
import {Employee} from "./Employee";

@Entity()
export class Department {

  @PrimaryGeneratedColumn()
  id: number

  @Column({
    length: 100
  })
  name: string

  @OneToMany(() => Employee, (employee) => employee.department, {
    cascade: true
  })
  employees: Employee[]

}

通过 OneToMany 装饰器的第二个参数指定外键列在 employee.department 维护

修改 index.ts:

import { AppDataSource } from "./data-source"
import {Department} from "./entity/Department";
import {Employee} from "./entity/Employee";

AppDataSource.initialize().then(async () => {


  const em1 = new Employee()
  em1.name = '三十11'

  const em2 = new Employee()
  em2.name = '三十22'

  const em3 = new Employee()
  em3.name = '三十33'

  const em4 = new Employee()
  em4.name = '三十44'


  const dept1 = new Department()
  dept1.name = '研发部'
  dept1.employees = [em1, em2, em3, em4]

  await AppDataSource.manager.save(dept1)


}).catch(error => console.log(error))

执行 npm run start:

image-20251021083530185.png

关联查询

import { AppDataSource } from "./data-source"
import {Department} from "./entity/Department";
import {Employee} from "./entity/Employee";

AppDataSource.initialize().then(async () => {
  // 关联查询
  const deptList = await AppDataSource.manager.find(Department, {
    relations: {
      employees: true
    }
  })
  console.log(deptList)

}).catch(error => console.log(error))

执行 npm run start

image-20251021132834180.png

删除

设置级联关系

删除 Department 之前,需要先把关联的 Employee 删除掉;如果设置了级联关系则可以省略这一步,只要删了 Department,mysql 会自动把关联的 Employee 记录删除,或者是把它们的外键 id 置为空。

import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm"
import {Department} from "./Department";

@Entity()
export class Employee {

  @PrimaryGeneratedColumn()
  id: number

  @Column({
    length: 100
  })
  name: string

  @ManyToOne(() => Department, {
    // cascade: true // 级联保存
    onDelete: 'CASCADE'
  })
  department: Department

}

在 index.ts 中增加删除

import { AppDataSource } from "./data-source"
import {Department} from "./entity/Department";
import {Employee} from "./entity/Employee";

AppDataSource.initialize().then(async () => {
  await AppDataSource.manager.delete(Department, 1);

  const deptListAfterDelete = await AppDataSource.manager.find(Department, {
    relations: {
      employees: true
    }
  })
  console.log(deptListAfterDelete)

}).catch(error => console.log(error))

执行 npm run start

image-20251021133840868.png