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

Typeorm(Nestjs) QueryBuilder更新多对多关系触发非空约束报错

TypeORM多对多关联添加触发非空约束错误问题

问题现象

在TypeORM中配置多对多关系后,使用QueryBuilder的relation API添加关联时,触发数据库非空约束错误,生成的SQL中usersId字段使用DEFAULT值(为空),导致关联表插入失败。

报错详情

执行的关键SQL及错误场景:

query: SELECT "Enterprise"."id" AS "Enterprise_id", "Enterprise"."createdAt" AS "Enterprise_createdAt", "Enterprise"."updatedAt" AS "Enterprise_updatedAt", "Enterprise"."name" AS "Enterprise_name", "Enterprise"."erpCustomerCode" AS "Enterprise_erpCustomerCode", "Enterprise"."isActive" AS "Enterprise_isActive", "Enterprise"."street" AS "Enterprise_street", "Enterprise"."houseNumber" AS "Enterprise_houseNumber", "Enterprise"."city" AS "Enterprise_city", "Enterprise"."state" AS "Enterprise_state", "Enterprise"."country" AS "Enterprise_country", "Enterprise"."zip" AS "Enterprise_zip", "Enterprise"."geoLocation" AS "Enterprise_geoLocation", "Enterprise"."unitsSystem" AS "Enterprise_unitsSystem", "Enterprise"."weekNumberingSystem" AS "Enterprise_weekNumberingSystem", "Enterprise"."yearStart" AS "Enterprise_yearStart", "Enterprise"."weekStart" AS "Enterprise_weekStart" FROM "enterprises" "Enterprise" WHERE ("Enterprise"."id" = $1) LIMIT 1 -- PARAMETERS: ["d2f16db8-63be-4b09-a5ea-e04336069df4"]
User {}
query: INSERT INTO "enterprise_users"("enterprisesId", "usersId") VALUES ($1, DEFAULT) -- PARAMETERS: ["d2f16db8-63be-4b09-a5ea-e04336069df4"]
query failed: INSERT INTO "enterprise_users"("enterprisesId", "usersId") VALUES ($1, DEFAULT) -- PARAMETERS: ["d2f16db8-63be-4b09-a5ea-e04336069df4"]

错误提示截图:
非空约束错误

实体与业务代码配置

enterprise.entity.ts

@Entity('enterprises')
export class Enterprise {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;

  @UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  updatedAt: Date;

  @Column({ unique: true, length: 50 })
  name: string;

  // 省略其他字段

  @ManyToMany(() => User, (users) => users.enterprises, { cascade: true })
  @JoinTable({ name: 'enterprise_users' })
  users: User[];
}

user.entity.ts

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @ManyToMany(() => Enterprise, (enterprise) => enterprise.users)
  enterprises: Enterprise[];
}

enterprise.service.ts

@Injectable()
export class EnterpriseService {
  constructor(
    @InjectRepository(Enterprise)
    private enterpriseRepository: Repository<Enterprise>,
  ) {}

  async update(id: string, enterprise: UpdateEnterpriseDto) {
    // 测试用新用户
    const user = new User();
    user.name = 'test123';

    // 触发错误的代码
    await this.enterpriseRepository.createQueryBuilder().relation(Enterprise, 'users').of(id).add(user);

    return this.enterpriseRepository.save({ id, ...enterprise });
  }
}

问题解答

猜想确认

你的猜想完全正确:必须先将User实体保存到数据库(生成主键ID),才能进行多对多关联操作。

无法自动创建User的原因

虽然在Enterprise的ManyToMany关系上配置了{ cascade: true },但这个级联规则仅对TypeORM的save方法生效。而QueryBuilder的relation API是直接操作中间关联表的底层方法,它默认认为传入的关联实体已经存在于数据库中(拥有有效主键ID),不会自动触发级联创建新实体的逻辑。当传入未保存的User对象时,其id为空,TypeORM只能在SQL中给usersId设置DEFAULT值,最终触发数据库的非空约束错误。

解决方案

方案1:先保存User,再执行关联

@Injectable()
export class EnterpriseService {
  constructor(
    @InjectRepository(Enterprise)
    private enterpriseRepository: Repository<Enterprise>,
    @InjectRepository(User) // 注入User仓库
    private userRepository: Repository<User>,
  ) {}

  async update(id: string, enterprise: UpdateEnterpriseDto) {
    const user = new User();
    user.name = 'test123';
    // 先保存用户到数据库,获取带ID的实体
    const savedUser = await this.userRepository.save(user);
    // 执行关联操作
    await this.enterpriseRepository.createQueryBuilder().relation(Enterprise, 'users').of(id).add(savedUser);

    return this.enterpriseRepository.save({ id, ...enterprise });
  }
}

方案2:利用save方法的级联特性

直接通过save方法更新企业实体,借助配置的cascade: true自动创建User并完成关联:

@Injectable()
export class EnterpriseService {
  constructor(
    @InjectRepository(Enterprise)
    private enterpriseRepository: Repository<Enterprise>,
  ) {}

  async update(id: string, enterprise: UpdateEnterpriseDto) {
    const user = new User();
    user.name = 'test123';
    // 查询目标企业并加载已关联用户,避免覆盖
    const targetEnterprise = await this.enterpriseRepository.findOne({ 
      where: { id },
      relations: ['users']
    });
    // 添加新用户到关联列表
    targetEnterprise.users = [...(targetEnterprise.users || []), user];
    // 保存时触发级联创建User并更新关联表
    await this.enterpriseRepository.save(targetEnterprise);

    return this.enterpriseRepository.save({ id, ...enterprise });
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 05:06:28