实体继承是面向对象编程的重要特性,TypeORM 提供了多种继承模式,让开发者能够更好地组织和管理实体类。
继承模式概述
TypeORM 支持三种主要的继承模式:
- 单表继承 (Single Table Inheritance)
- 类表继承 (Class Table Inheritance)
- 具体表继承 (Concrete Table Inheritance)
单表继承 (Single Table Inheritance)
基本概念
单表继承将所有子类的数据存储在同一个表中,使用鉴别列(discriminator column)来区分不同的子类。
实现示例
typescriptimport { Entity, PrimaryGeneratedColumn, Column, ChildEntity, DiscriminatorColumn, DiscriminatorValue } from 'typeorm'; @Entity() @DiscriminatorColumn({ name: 'type' }) export class Content { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column({ type: 'text' }) body: string; @Column({ type: 'timestamp' }) createdAt: Date; @Column({ type: 'timestamp' }) updatedAt: Date; } @ChildEntity() @DiscriminatorValue('article') export class Article extends Content { @Column() author: string; @Column() category: string; } @ChildEntity() @DiscriminatorValue('video') export class Video extends Content { @Column() url: string; @Column() duration: number; } @ChildEntity() @DiscriminatorValue('podcast') export class Podcast extends Content { @Column() audioUrl: string; @Column() episodeNumber: number; }
使用示例
typescript// 查询所有内容 const allContent = await dataSource.getRepository(Content).find(); // 查询特定类型的内容 const articles = await dataSource.getRepository(Article).find(); const videos = await dataSource.getRepository(Video).find(); // 创建不同类型的内容 const article = new Article(); article.title = 'TypeORM Tutorial'; article.body = 'Content...'; article.author = 'John Doe'; article.category = 'Programming'; const video = new Video(); video.title = 'TypeORM Video Guide'; video.body = 'Video content...'; video.url = 'https://example.com/video'; video.duration = 600; await dataSource.getRepository(Content).save([article, video]);
优缺点
优点:
- 查询性能好,所有数据在一个表中
- 简单的关联关系
- 适合子类字段差异不大的情况
缺点:
- 表可能有很多 NULL 列
- 不适合子类字段差异很大的情况
- 添加新子类需要修改表结构
类表继承 (Class Table Inheritance)
基本概念
类表继承为每个类创建单独的表,子类表通过外键关联到父类表。
实现示例
typescriptimport { Entity, PrimaryGeneratedColumn, Column, ChildEntity, TableInheritance } from 'typeorm'; @Entity() @TableInheritance({ column: { type: 'varchar', name: 'type' } }) export class Person { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; @Column({ type: 'date' }) birthDate: Date; } @ChildEntity() export class Employee extends Person { @Column() position: string; @Column() department: string; @Column({ type: 'decimal', precision: 10, scale: 2 }) salary: number; } @ChildEntity() export class Customer extends Person { @Column() companyName: string; @Column() industry: string; @Column({ type: 'int' }) loyaltyPoints: number; }
使用示例
typescript// 查询所有人员 const allPeople = await dataSource.getRepository(Person).find(); // 查询特定类型的人员 const employees = await dataSource.getRepository(Employee).find(); const customers = await dataSource.getRepository(Customer).find(); // 创建不同类型的人员 const employee = new Employee(); employee.name = 'John Doe'; employee.email = 'john@example.com'; employee.birthDate = new Date('1990-01-01'); employee.position = 'Developer'; employee.department = 'IT'; employee.salary = 75000; const customer = new Customer(); customer.name = 'Jane Smith'; customer.email = 'jane@example.com'; customer.birthDate = new Date('1985-05-15'); customer.companyName = 'Acme Corp'; customer.industry = 'Technology'; customer.loyaltyPoints = 1000; await dataSource.getRepository(Person).save([employee, customer]);
优缺点
优点:
- 数据规范化,避免 NULL 列
- 每个子类有独立的表
- 适合子类字段差异大的情况
缺点:
- 查询需要 JOIN,性能可能较差
- 复杂的关联关系
- 添加新子类需要创建新表
具体表继承 (Concrete Table Inheritance)
基本概念
具体表继承为每个具体子类创建独立的表,不创建父类表。
实现示例
typescriptimport { Entity, PrimaryGeneratedColumn, Column, AbstractEntity } from 'typeorm'; @AbstractEntity() export abstract class Vehicle { @PrimaryGeneratedColumn() id: number; @Column() brand: string; @Column() model: string; @Column({ type: 'int' }) year: number; @Column({ type: 'decimal', precision: 10, scale: 2 }) price: number; } @Entity() export class Car extends Vehicle { @Column() fuelType: string; @Column({ type: 'int' }) numberOfDoors: number; @Column({ type: 'boolean' }) isConvertible: boolean; } @Entity() export class Motorcycle extends Vehicle { @Column() engineType: string; @Column({ type: 'int' }) displacement: number; @Column({ type: 'boolean' }) hasSidecar: boolean; }
使用示例
typescript// 查询特定类型的车辆 const cars = await dataSource.getRepository(Car).find(); const motorcycles = await dataSource.getRepository(Motorcycle).find(); // 创建不同类型的车辆 const car = new Car(); car.brand = 'Toyota'; car.model = 'Camry'; car.year = 2024; car.price = 35000; car.fuelType = 'Hybrid'; car.numberOfDoors = 4; car.isConvertible = false; const motorcycle = new Motorcycle(); motorcycle.brand = 'Harley-Davidson'; motorcycle.model = 'Street 750'; motorcycle.year = 2024; motorcycle.price = 8000; motorcycle.engineType = 'V-Twin'; motorcycle.displacement = 750; motorcycle.hasSidecar = false; await Promise.all([ dataSource.getRepository(Car).save(car), dataSource.getRepository(Motorcycle).save(motorcycle) ]);
优缺点
优点:
- 每个表都是完整的,没有 NULL 列
- 查询性能好
- 适合子类完全独立的情况
缺点:
- 父类字段在所有子类表中重复
- 难以查询所有子类的数据
- 不适合需要统一查询父类的情况
继承与关系
在继承中使用关系
typescript@Entity() @DiscriminatorColumn({ name: 'type' }) export class Content { @PrimaryGeneratedColumn() id: number; @Column() title: string; @OneToMany(() => Comment, comment => comment.content) comments: Comment[]; } @ChildEntity() @DiscriminatorValue('article') export class Article extends Content { @Column() author: string; @Column() category: string; } @Entity() export class Comment { @PrimaryGeneratedColumn() id: number; @Column() text: string; @ManyToOne(() => Content, content => content.comments) content: Content; } // 使用示例 const article = await dataSource.getRepository(Article).findOne({ where: { id: 1 }, relations: ['comments'] });
多态关系
typescript@Entity() export class Tag { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() entityType: string; // 'article' or 'video' @Column() entityId: number; } @Entity() @DiscriminatorColumn({ name: 'type' }) export class Content { @PrimaryGeneratedColumn() id: number; @Column() title: string; @OneToMany(() => Tag, tag => tag.entityId = this.id && tag.entityType = 'article' ) tags: Tag[]; } @ChildEntity() @DiscriminatorValue('article') export class Article extends Content { @Column() author: string; } @ChildEntity() @DiscriminatorValue('video') export class Video extends Content { @Column() url: string; }
继承与查询
查询所有子类
typescript// 单表继承:查询所有内容 const allContent = await dataSource.getRepository(Content).find(); // 类表继承:查询所有人员 const allPeople = await dataSource.getRepository(Person).find({ relations: true // 加载子类数据 }); // 具体表继承:需要分别查询 const cars = await dataSource.getRepository(Car).find(); const motorcycles = await dataSource.getRepository(Motorcycle).find();
按类型过滤
typescript// 使用鉴别器值过滤 const articles = await dataSource.getRepository(Content).find({ where: { type: 'article' as any } }); // 或者使用子类 Repository const articles = await dataSource.getRepository(Article).find();
复杂查询
typescript// 查询特定类型的关联数据 const articlesWithComments = await dataSource .getRepository(Article) .createQueryBuilder('article') .leftJoinAndSelect('article.comments', 'comment') .where('article.category = :category', { category: 'Technology' }) .getMany(); // 使用 QueryBuilder 按类型查询 const contents = await dataSource .getRepository(Content) .createQueryBuilder('content') .where('content.type IN (:...types)', { types: ['article', 'video'] }) .getMany();
继承与迁移
单表继承迁移
typescriptimport { MigrationInterface, QueryRunner, Table } from 'typeorm'; export class CreateContentTable1234567890123 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.createTable( new Table({ name: 'content', columns: [ { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment', }, { name: 'title', type: 'varchar', }, { name: 'body', type: 'text', }, { name: 'type', // 鉴别列 type: 'varchar', default: "'article'", }, { name: 'author', // Article 特有字段 type: 'varchar', isNullable: true, }, { name: 'category', // Article 特有字段 type: 'varchar', isNullable: true, }, { name: 'url', // Video 特有字段 type: 'varchar', isNullable: true, }, { name: 'duration', // Video 特有字段 type: 'int', isNullable: true, }, ], }), true ); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropTable('content'); } }
类表继承迁移
typescriptexport class CreatePersonTables1234567890124 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { // 创建父类表 await queryRunner.createTable( new Table({ name: 'person', columns: [ { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment', }, { name: 'name', type: 'varchar', }, { name: 'email', type: 'varchar', }, { name: 'birthDate', type: 'date', }, { name: 'type', // 鉴别列 type: 'varchar', }, ], }), true ); // 创建子类表 await queryRunner.createTable( new Table({ name: 'employee', columns: [ { name: 'id', type: 'int', isPrimary: true, }, { name: 'position', type: 'varchar', }, { name: 'department', type: 'varchar', }, { name: 'salary', type: 'decimal', precision: 10, scale: 2, }, ], foreignKeys: [ { columnNames: ['id'], referencedColumnNames: ['id'], referencedTableName: 'person', onDelete: 'CASCADE', }, ], }), true ); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropTable('employee'); await queryRunner.dropTable('person'); } }
最佳实践
选择合适的继承模式
typescript// ✅ 单表继承:子类字段差异小 @Entity() @DiscriminatorColumn({ name: 'type' }) export class User { @PrimaryGeneratedColumn() id: number; @Column() username: string; @Column() email: string; } @ChildEntity() @DiscriminatorValue('admin') export class Admin extends User { @Column() permissions: string; } // ✅ 类表继承:子类字段差异大 @Entity() @TableInheritance({ column: { type: 'varchar', name: 'type' } }) export class Product { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column({ type: 'decimal', precision: 10, scale: 2 }) price: number; } @ChildEntity() export class PhysicalProduct extends Product { @Column() weight: number; @Column() dimensions: string; } @ChildEntity() export class DigitalProduct extends Product { @Column() downloadUrl: string; @Column() fileSize: number; } // ✅ 具体表继承:子类完全独立 @AbstractEntity() export abstract class Notification { @PrimaryGeneratedColumn() id: number; @Column() message: string; @Column({ type: 'timestamp' }) createdAt: Date; } @Entity() export class EmailNotification extends Notification { @Column() recipientEmail: string; @Column() subject: string; } @Entity() export class SMSNotification extends Notification { @Column() phoneNumber: string; @Column() sender: string; }
避免深层继承
typescript// ❌ 不好的做法:深层继承 @Entity() @DiscriminatorColumn({ name: 'type' }) export class Animal { @PrimaryGeneratedColumn() id: number; @Column() name: string; } @ChildEntity() @DiscriminatorValue('mammal') export class Mammal extends Animal { @Column() furColor: string; } @ChildEntity() @DiscriminatorValue('dog') export class Dog extends Mammal { @Column() breed: string; } // ✅ 好的做法:扁平继承结构 @Entity() @DiscriminatorColumn({ name: 'type' }) export class Animal { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column({ nullable: true }) furColor: string; @Column({ nullable: true }) breed: string; } @ChildEntity() @DiscriminatorValue('dog') export class Dog extends Animal { // Dog 特有的属性 } @ChildEntity() @DiscriminatorValue('cat') export class Cat extends Animal { // Cat 特有的属性 }
TypeORM 的实体继承功能提供了强大的面向对象编程支持,合理使用继承可以提高代码的可维护性和可扩展性。