2月18日 22:20

How to define and use relationship mappings in TypeORM? Detailed configuration for one-to-one, one-to-many, and many-to-many relationships

TypeORM provides four main types of relationship mappings, each with its specific use cases and configuration methods. Understanding these relationship mappings is crucial for building complex data models.

Four Relationship Types

1. One-to-One

A one-to-one relationship represents a unique correspondence between two entities. For example, a user can have only one profile.

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; }

Key Points:

  • Use @OneToOne() decorator to define the relationship
  • Use @JoinColumn() on the owning side to specify the foreign key column
  • cascade: true allows cascade operations (save, delete, etc.)

2. One-to-Many / Many-to-One

One-to-many relationships are the most common type. For example, a user can publish multiple posts.

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[]; }

Key Points:

  • @ManyToOne() is placed on the "many" side and contains the foreign key
  • @OneToMany() is placed on the "one" side and doesn't need @JoinColumn()
  • Foreign key is automatically added to the "many" side table

3. Many-to-Many

Many-to-many relationships require an intermediate table to connect two entities. For example, a post can have multiple tags, and a tag can belong to multiple posts.

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[]; }

Key Points:

  • Use @JoinTable() on one side of the relationship to define the intermediate table
  • Intermediate table is automatically created with two foreign keys
  • Can customize intermediate table name and column names

Relationship Configuration Options

Eager and Lazy Loading

typescript
@Entity() export class User { @OneToMany(() => Post, post => post.author, { eager: true // Load related data immediately }) posts: Post[]; } // Or use lazy loading @Entity() export class User { @OneToMany(() => Post, post => post.author) posts: Promise<Post[]>; }

Cascade Operations

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

Cascade operation options:

  • insert: Automatically save child entities when saving parent entity
  • update: Automatically update child entities when updating parent entity
  • remove: Automatically delete child entities when deleting parent entity
  • soft-remove: Soft delete
  • recover: Recover soft-deleted entities

OnDelete and OnUpdate

typescript
@ManyToOne(() => User, user => user.posts, { onDelete: 'CASCADE', // Cascade delete posts when user is deleted onUpdate: 'CASCADE' // Cascade update post author ID when user ID is updated }) author: User;

Relationship Queries

typescript
const userRepository = dataSource.getRepository(User); // Load related data const users = await userRepository.find({ relations: ['posts', 'profile'] }); // Conditional query on related data const usersWithPosts = await userRepository.find({ relations: { posts: true }, where: { posts: { title: Like('%TypeORM%') } } });

Using 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();

Custom Relationships

Custom JoinTable

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

Custom JoinColumn

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

Best Practices

  1. Choose relationship types wisely: Select the most appropriate relationship type based on business requirements
  2. Avoid overusing Eager loading: May cause N+1 query problems
  3. Use cascade delete carefully: Ensure important data is not accidentally deleted
  4. Optimize queries with indexes: Add indexes to foreign key columns
  5. Consider performance impact: Complex relationship queries may affect performance

TypeORM's relationship mapping system provides powerful and flexible ways to handle relationships between entities. Mastering these concepts is crucial for building efficient and maintainable applications.

标签:TypeORM