TypeORM多对多关系添加重复关联实体报唯一约束错误如何解决
TypeORM多对多关系允许重复关联实体的解决方案
报错原因
TypeORM默认的@ManyToMany会自动生成中间关联表,且该表默认使用两个关联实体的主键作为联合唯一主键,因此同一个workout和exercise的组合仅允许存在一条关联记录,重复插入时就会触发duplicate key value violates unique constraint错误。
原生多对多关系本身不支持重复关联,要实现同个workout下可添加多个相同exercise,需要自定义中间关联实体,将原多对多关系拆分为两个一对多关系实现。
具体实现步骤
1. 自定义中间表实体
新增独立的中间表实体(示例命名为WorkoutExercise),单独设置自增主键,不再使用联合主键,同时分别和Workout、Exercise建立多对一关联:
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; import { Workout } from './workout.entity'; import { Exercise } from './exercise.entity'; @Entity() export class WorkoutExercise { // 单独的自增主键,避免联合主键的唯一约束限制 @PrimaryGeneratedColumn() id: number; @ManyToOne(() => Workout, workout => workout.workoutExercises) workout: Workout; @Column() workoutId: number; @ManyToOne(() => Exercise, exercise => exercise.workoutExercises) exercise: Exercise; @Column() exerciseId: number; // 可按需扩展其他字段,比如该动作在当前训练中的组数、次数、重量等 @Column({ default: 3 }) sets: number; @Column({ default: 12 }) reps: number; }
2. 修改原有实体的关联配置
删除原来的@ManyToMany注解,替换为和中间表的一对多关联:
修改后的Workout实体
import { Entity, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { WorkoutExercise } from './workout-exercise.entity'; @Entity() export class Workout { @PrimaryGeneratedColumn() id: number; // 替换原多对多关联,cascade设置为true可在保存workout时自动保存关联的中间表记录 @OneToMany(() => WorkoutExercise, workoutExercise => workoutExercise.workout, { cascade: true }) workoutExercises: WorkoutExercise[]; }
修改后的Exercise实体
import { Entity, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { WorkoutExercise } from './workout-exercise.entity'; @Entity() export class Exercise { @PrimaryGeneratedColumn() id: number; @OneToMany(() => WorkoutExercise, workoutExercise => workoutExercise.exercise) workoutExercises: WorkoutExercise[]; }
3. 新增数据示例
创建带重复exercise的workout时,直接给workoutExercises字段赋值对应关联即可,不会触发唯一约束错误:
// 业务层示例代码 const newWorkout = new Workout(); newWorkout.workoutExercises = [ { exerciseId: 1, sets: 3, reps: 12 }, { exerciseId: 1, sets: 2, reps: 15 }, // 重复添加id为1的exercise,可正常保存 { exerciseId: 2, sets: 4, reps: 8 } ]; await this.workoutRepository.save(newWorkout);
4. 查询数据示例
查询workout关联的所有exercise时,关联中间表和exercise实体即可:
const workoutDetail = await this.workoutRepository.findOne({ where: { id: 1 }, relations: { workoutExercises: { exercise: true } } });
内容的提问来源于stack exchange,提问作者L. Baptiste
相关产品推荐
相关产品推荐

