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 查询关联数据
typescriptconst 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
typescriptconst 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;
最佳实践
- 合理选择关系类型: 根据业务需求选择最合适的关系类型
- 避免过度使用 Eager 加载: 可能导致 N+1 查询问题
- 谨慎使用级联删除: 确保不会意外删除重要数据
- 使用索引优化查询: 为外键列添加索引
- 考虑性能影响: 复杂关系查询可能影响性能
TypeORM 的关系映射系统提供了强大而灵活的方式来处理实体之间的关系,掌握这些概念对于构建高效、可维护的应用程序至关重要。