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

NestJS+TypeORM实现MySQL带前缀自增主键与外键关联

解决方案:带前缀自增ID + 自动外键关联

一、实现带前缀的自增ID

方案1:实体钩子 + 数据库自增字段

这种方式依赖数据库自增数字字段,在插入前通过实体钩子拼接前缀生成最终ID,简单易维护。

RevenueGenerator 实体

import { Entity, Column, BeforeInsert, PrimaryColumn } from 'typeorm';

@Entity()
export class RevenueGenerator {
  @PrimaryColumn()
  id: string; // 最终生成 RG101、RG102 格式的ID

  // 数据库自增的数字字段,仅用于生成前缀ID,对外隐藏
  @Column({ type: 'int', unsigned: true, generated: 'increment', unique: true })
  private autoId: number;

  @Column()
  name: string;

  @Column()
  audience: string;

  // 插入前自动生成带前缀的ID
  @BeforeInsert()
  generateId() {
    this.id = `RG${this.autoId.toString().padStart(3, '0')}`;
  }
}

RevenueStream 实体

import { Entity, Column, BeforeInsert, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
import { RevenueGenerator } from './revenue-generator.entity';

@Entity()
export class RevenueStream {
  @PrimaryColumn()
  id: string; // 最终生成 RS101、RS102 格式的ID

  @Column({ type: 'int', unsigned: true, generated: 'increment', unique: true })
  private autoId: number;

  @Column()
  tax: number;

  @Column()
  sharepercentage: number;

  @ManyToOne(() => RevenueGenerator)
  @JoinColumn({ name: 'revenueGeneratorId' })
  revenueGenerator: RevenueGenerator;

  @BeforeInsert()
  generateId() {
    this.id = `RS${this.autoId.toString().padStart(3, '0')}`;
  }
}

方案2:自定义ID生成器

如果不想额外增加自增字段,可通过TypeORM的自定义ID生成策略,直接从现有ID中提取数字部分自增。

自定义生成器类

import { EntityManager, IDatabaseDriver, Connection } from 'typeorm';
import { AbstractIdGenerator } from 'typeorm/id/AbstractIdGenerator';

export class PrefixIdGenerator extends AbstractIdGenerator {
  async generateId(
    entity: any,
    manager: EntityManager<any>,
    connection: Connection<IDatabaseDriver<any>>,
  ): Promise<string> {
    // 根据实体类型设置前缀
    const prefix = entity.constructor.name === 'RevenueGenerator' ? 'RG' : 'RS';
    // 查询当前最大ID的数字部分,加悲观锁防止并发重复
    const maxEntity = await manager
      .createQueryBuilder(entity.constructor, 'e')
      .select('MAX(CAST(SUBSTRING(e.id, 3) AS UNSIGNED))', 'maxId')
      .setLock('pessimistic_write')
      .getRawOne();
    // 从101开始自增,不足3位补0
    const nextId = (maxEntity.maxId || 100) + 1;
    return `${prefix}${nextId.toString().padStart(3, '0')}`;
  }
}

实体中配置生成器

// RevenueGenerator 实体
@Entity()
export class RevenueGenerator {
  @PrimaryColumn({
    type: 'varchar',
    generated: 'custom',
    generator: { type: PrefixIdGenerator },
  })
  id: string;

  @Column()
  name: string;

  @Column()
  audience: string;
}

// RevenueStream 实体
@Entity()
export class RevenueStream {
  @PrimaryColumn({
    type: 'varchar',
    generated: 'custom',
    generator: { type: PrefixIdGenerator },
  })
  id: string;

  @Column()
  tax: number;

  @Column()
  sharepercentage: number;

  @ManyToOne(() => RevenueGenerator)
  @JoinColumn({ name: 'revenueGeneratorId' })
  revenueGenerator: RevenueGenerator;
}

二、自动关联外键

避免数据库触发器的不可控性,直接在应用层(Service)处理关联逻辑,确保外键自动插入:

Service 层代码示例

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { RevenueGenerator } from './entities/revenue-generator.entity';
import { RevenueStream } from './entities/revenue-stream.entity';

@Injectable()
export class RevenueService {
  constructor(
    @InjectRepository(RevenueGenerator)
    private readonly rgRepo: Repository<RevenueGenerator>,
    @InjectRepository(RevenueStream)
    private readonly rsRepo: Repository<RevenueStream>,
  ) {}

  // 同时创建RevenueGenerator和关联的RevenueStream
  async createWithRelation(rgData: any, rsData: any) {
    // 1. 先创建并保存RevenueGenerator,获取自动生成的ID
    const rg = this.rgRepo.create(rgData);
    await this.rgRepo.save(rg);

    // 2. 关联外键,创建并保存RevenueStream
    const rs = this.rsRepo.create({
      ...rsData,
      revenueGenerator: rg, // 直接关联实体,TypeORM会自动写入外键字段
    });
    await this.rsRepo.save(rs);

    return { revenueGenerator: rg, revenueStream: rs };
  }
}

注意事项

  • 若使用自定义ID生成器,必须加悲观锁(setLock('pessimistic_write')),防止高并发场景下生成重复ID。
  • 实体关联需正确配置@ManyToOne和@JoinColumn,确保TypeORM能自动处理外键映射。
  • 优先选择应用层处理关联逻辑,比数据库触发器更易调试和维护。

内容的提问来源于stack exchange,提问作者Mahalingam Ramasamy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 00:57:14