乐闻世界logo
搜索文章和话题

TypeORM 中如何定义和使用关系映射?包括一对一、一对多、多对多关系的详细配置

2月18日 22:20

TypeORM 提供了四种主要的关系映射类型,每种关系都有其特定的使用场景和配置方式。理解这些关系映射对于构建复杂的数据模型至关重要。

四种关系类型

1. One-to-One (一对一)

一对一关系表示两个实体之间存在唯一的对应关系。例如,一个用户只能有一个个人资料。

typescript
@Entity() export class Profile { @PrimaryGeneratedColumn() id: number; @Column() gender: string; @Column() bio: string; @OneToOne(() => User, user => user.profile) @JoinColumn() user: User; } @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToOne(() => Profile, profile => profile.user, { cascade: true }) profile: Profile; }

关键点:

  • 使用 @OneToOne() 装饰器定义关系
  • 在拥有方使用 @JoinColumn() 指定外键列
  • cascade: true 允许级联操作(保存、删除等)

2. One-to-Many / Many-to-One (一对多/多对一)

一对多关系是最常见的关系类型。例如,一个用户可以发表多篇文章。

typescript
@Entity() export class Post { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column() content: string; @ManyToOne(() => User, user => user.posts) author: User; } @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @OneToMany(() => Post, post => post.author) posts: Post[]; }

关键点:

  • @ManyToOne() 放在"多"的一方,包含外键
  • @OneToMany() 放在"一"的一方,不需要 @JoinColumn()
  • 外键自动添加到"多"的一方表中

3. Many-to-Many (多对多)

多对多关系需要中间表来连接两个实体。例如,一篇文章可以有多个标签,一个标签也可以属于多篇文章。

typescript
@Entity() export class Tag { @PrimaryGeneratedColumn() id: number; @Column() name: string; @ManyToMany(() => Post, post => post.tags) posts: Post[]; } @Entity() export class Post { @PrimaryGeneratedColumn() id: number; @Column() title: string; @ManyToMany(() => Tag, tag => tag.posts, { cascade: true }) @JoinTable() tags: Tag[]; }

关键点:

  • 使用 @JoinTable() 在关系的一方定义中间表
  • 中间表自动创建,包含两个外键
  • 可以自定义中间表名称和列名

关系配置选项

Eager 和 Lazy 加载

typescript
@Entity() export class User { @OneToMany(() => Post, post => post.author, { eager: true // 立即加载关联数据 }) posts: Post[]; } // 或者使用懒加载 @Entity() export class User { @OneToMany(() => Post, post => post.author) posts: Promise<Post[]>; }

级联操作 (Cascade)

typescript
@OneToMany(() => Post, post => post.author, { cascade: ['insert', 'update', 'remove', 'soft-remove', 'recover'] }) posts: Post[];

级联操作选项:

  • insert: 保存父实体时自动保存子实体
  • update: 更新父实体时自动更新子实体
  • remove: 删除父实体时自动删除子实体
  • soft-remove: 软删除
  • recover: 恢复软删除的实体

OnDelete 和 OnUpdate

typescript
@ManyToOne(() => User, user => user.posts, { onDelete: 'CASCADE', // 删除用户时级联删除文章 onUpdate: 'CASCADE' // 更新用户ID时级联更新文章 }) author: User;

关系查询

使用 FindOptions 查询关联数据

typescript
const userRepository = dataSource.getRepository(User); // 加载关联数据 const users = await userRepository.find({ relations: ['posts', 'profile'] }); // 条件查询关联数据 const usersWithPosts = await userRepository.find({ relations: { posts: true }, where: { posts: { title: Like('%TypeORM%') } } });

使用 QueryBuilder

typescript
const users = await dataSource .getRepository(User) .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .leftJoinAndSelect('user.profile', 'profile') .where('post.title = :title', { title: 'TypeORM Guide' }) .getMany();

自定义关系

自定义 JoinTable

typescript
@ManyToMany(() => Tag, tag => tag.posts) @JoinTable({ name: 'post_tags', joinColumn: { name: 'postId', referencedColumnName: 'id' }, inverseJoinColumn: { name: 'tagId', referencedColumnName: 'id' } }) tags: Tag[];

自定义 JoinColumn

typescript
@ManyToOne(() => User, user => user.posts) @JoinColumn({ name: 'author_id', referencedColumnName: 'id' }) author: User;

最佳实践

  1. 合理选择关系类型: 根据业务需求选择最合适的关系类型
  2. 避免过度使用 Eager 加载: 可能导致 N+1 查询问题
  3. 谨慎使用级联删除: 确保不会意外删除重要数据
  4. 使用索引优化查询: 为外键列添加索引
  5. 考虑性能影响: 复杂关系查询可能影响性能

TypeORM 的关系映射系统提供了强大而灵活的方式来处理实体之间的关系,掌握这些概念对于构建高效、可维护的应用程序至关重要。

标签:TypeORM