Typeorm如何为拥有不同属性集的实体类型建模
TypeORM多类型实体结构实现方案
你当前采用的是标准**类表继承(Table Per Type)**模式,完全匹配你提出的三个核心要求,针对你提到的两个缺点,可通过以下方式优化:
1. 解决实体属性无法复用的问题
用@MappedSuperclass装饰器抽离公共字段定义,该装饰器的作用是将类中的字段定义继承给子类实体,但不会将这些字段映射到子类对应的数据表中,完美兼顾类型复用和分表设计。
示例代码如下:
// 抽离公共属性的基类,不会生成数据库表 @MappedSuperclass() export abstract class BaseItemProps { @PrimaryGeneratedColumn('uuid') id!: string; @Column() name!: string; @Column() icon!: string; @Column() stackAmount!: string; @Column() buyPrice!: number; @Column() sellPrice!: number; } // 主表实体 @Entity('items') export class ItemEntity extends BaseItemProps {} // 装备实体,继承BaseItemProps获得类型提示,表不会生成这些字段 @Entity('equipments') export class EquipmentEntity extends BaseItemProps { @OneToOne(() => ItemEntity, { primary: true }) @JoinColumn({ name: 'itemId' }) itemId!: ItemEntity; @Column() quality!: number; @Column() equipmentLevel!: number; @Column() requiredLevel!: number; } // 防具实体同理继承对应基类即可 @Entity('armour') export class ArmourEntity extends EquipmentEntity { @OneToOne(() => EquipmentEntity, { primary: true }) @JoinColumn({ name: 'equipmentId' }) equipmentId!: EquipmentEntity; @Column() blockRating!: number; }
2. 解决必须手写QueryBuilder调用getRawMany的问题
通过TypeORM自定义Repository封装查询和序列化逻辑,上层业务直接调用封装好的方法即可,无需感知底层实现。
示例代码如下:
import { plainToInstance } from 'class-transformer'; import { CustomRepository } from 'typeorm-typeorm-extensions'; @CustomRepository(ItemEntity) export class ItemRepository extends Repository<ItemEntity> { async findAllItemsWithDetails(): Promise<(WeaponEntity | ArmourEntity | TradeGoodsEntity | ItemEntity)[]> { const rawItems = await this.createQueryBuilder('item') .leftJoinAndSelect('equipments', 'eq', 'eq.itemId = item.id') .leftJoinAndSelect('armour', 'a', 'a.equipmentId = eq.itemId') .leftJoinAndSelect('weapons', 'w', 'w.equipmentId = eq.itemId') .leftJoinAndSelect('trade_goods', 'tg', 'tg.itemId = item.id') .getRawMany(); // 序列化raw数据为对应实体实例 return rawItems.map(raw => { if (raw.w_equipmentId) return plainToInstance(WeaponEntity, { ...raw }); if (raw.a_equipmentId) return plainToInstance(ArmourEntity, { ...raw }); if (raw.tg_itemId) return plainToInstance(TradeGoodsEntity, { ...raw }); return plainToInstance(ItemEntity, raw); }) } }
方案验证
该实现完全符合你的三个核心目标:
- 所有Item统一使用
items表的主键ID,全局唯一 - 数据库各表职责拆分清晰,每个表对应一类实体的独有字段,结构完全自解释
- 上层业务调用封装好的
findAllItemsWithDetails方法即可一次拉取所有类型的Item,无需额外处理
内容的提问来源于stack exchange,提问作者Vasily Polonsky
相关产品推荐
相关产品推荐

