You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 22:06:03