SpringBoot嵌套保存实体触发PostgreSQL外键约束异常求助
问题
嵌套保存包含子实体的Module时触发PostgreSQL外键约束错误:
org.postgresql.util.PSQLException: ERROR: insert or update on table violates foreign key constraint
单独保存各实体正常,但级联嵌套保存失败。相关实体代码、接口及请求JSON如下:
实体代码
@Entity(name = "question") @NoArgsConstructor @AllArgsConstructor @Setter @Getter @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) public class Question extends PersistableObject{ @Id Long id; @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST }) @JoinColumn(name = "id") private List<Answer> answers = new ArrayList<>(); Date created = new Date(); private String description; @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE }) @JoinColumn(name = "id", insertable = false, updatable = false) @JsonIgnore private Module module; //getters and setters // constructors } @Entity(name = "answer") @NoArgsConstructor @AllArgsConstructor @Setter @Getter public class Answer extends PersistableObject { @ManyToOne @JoinColumn Question nextQuestion; @ManyToOne @JoinColumn(name = "id_question") Question prevQuestion; private Date created = new Date(); @Basic private String description; @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE }) @JoinColumn(name = "module", referencedColumnName = "id", insertable = false, updatable = false) private Module module; } @Entity(name = "module") @NoArgsConstructor @AllArgsConstructor @Setter @Getter public class Module extends PersistableObject { String description; @OneToMany(fetch = FetchType.LAZY,mappedBy = "module",cascade = { CascadeType.MERGE, CascadeType.ALL }) private List<Answer> answers = new ArrayList<>(); @OneToMany(fetch = FetchType.LAZY,mappedBy = "module",cascade = { CascadeType.MERGE, CascadeType.ALL }) private List<Question> questions = new ArrayList<>(); } @MappedSuperclass @Getter @Setter @EqualsAndHashCode public abstract class PersistableObject extends RepresentationModel<PersistableObject> { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; Date created = new Date(); }
接口代码
@PostMapping("/") public ResponseEntity<Module> create(@RequestBody Module module) { Module result = moduleService.save(module); URI uri = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}") .buildAndExpand(result.getId()) .toUri(); return ResponseEntity .created(uri) .body(result); }
请求JSON
{ "id": 1, "created": "2022-11-23T08:24:30.878+00:00", "description": "MDD", "answers": [ { "id": 1, "created": "2022-11-23T08:24:30.878+00:00", "description": "AN1", "moduleId": 1 }, { "id": 2, "created": "2022-11-23T08:24:30.878+00:00", "description": "AN2", "moduleId": 1 } ], "questions": [ { "id": 1, "created": "2022-11-23T08:24:30.878+00:00", "answers": [ { "id": 3, "created": "2022-11-23T08:24:30.878+00:00", "description": "AN1", "moduleId": 1 }, { "id": 4, "created": "2022-11-23T08:24:30.878+00:00", "description": "AN2", "moduleId": 1 } ], "description": "Q1" } ] }
解决方案
1. 修复外键映射错误
- Question与Module的关联:
@JoinColumn(name = "id")错误指向了Question自身的主键,应改为映射到Module的外键列(比如module_id),并移除insertable = false, updatable = false限制:@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE }) @JoinColumn(name = "module_id") @JsonIgnore private Module module; - Answer与Module的关联:
insertable = false, updatable = false导致级联保存时无法写入外键值,同时规范列名为module_id:@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE }) @JoinColumn(name = "module_id", referencedColumnName = "id") private Module module; - Question与Answer的关联:
@JoinColumn(name = "id")错误,应指向Answer表中关联Question的外键列(比如question_id):@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST }, orphanRemoval = true) @JoinColumn(name = "question_id") private List<Answer> answers = new ArrayList<>();
2. 维护双向关联引用
级联保存时,必须手动维护双向关联的双方引用,否则Hibernate无法正确写入外键。在moduleService.save方法中添加逻辑:
module.getQuestions().forEach(question -> { question.setModule(module); // 同时维护Question下Answer的关联 question.getAnswers().forEach(answer -> { answer.setModule(module); answer.setPrevQuestion(question); }); }); module.getAnswers().forEach(answer -> answer.setModule(module));
3. 修正实体重复字段
Question和Answer类中重复定义了created字段,与父类PersistableObject的created冲突,直接移除子类中的该字段,统一使用父类的属性。
4. 调整请求JSON
- 移除所有手动指定的
id字段:实体主键是自增策略,由数据库自动生成,手动指定会导致主键冲突。 - 确保
moduleId与实体属性映射:在Answer和Question的module属性上添加@JsonProperty("moduleId")注解,让Jackson能正确解析JSON中的字段。
5. 确认级联类型
Module类中对Question和Answer的级联使用CascadeType.ALL已经包含了持久化、更新等操作,无需额外调整,但建议添加orphanRemoval = true确保删除父实体时自动清理子实体。
内容的提问来源于stack exchange,提问作者Learner12
相关产品推荐
相关产品推荐

