JPA保存分类父子关系时报主键重复,问题出在哪?
问题描述
定义了如下Category实体,尝试用@OneToOne和@OneToMany注解维护分类的父子关系,并配置了PERSIST、MERGE级联操作。编写单元测试保存包含两个子分类的父分类时,触发PostgreSQL数据库的唯一约束冲突错误,提示parent_id重复,疑惑配置的级联操作为何未能阻止该问题。
实体代码
import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.HashSet; import java.util.Set; @Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(length = 128, nullable = false, unique = true) private String name; @Column(length = 64, nullable = false, unique = true) private String alias; @Column(length = 1000 , nullable = false) private String image; private boolean enabled; @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }) @JoinColumn(name="parent_id") private Category parent; @OneToMany(mappedBy = "parent",cascade = { CascadeType.PERSIST, CascadeType.MERGE }) private Set<Category> children = new HashSet<>(); public Category(String name, String alias, String image) { this.name = name; this.alias = alias; this.image = image; } }
单元测试代码
@Test public void whenCategoryWithSameSubCategoriesIsCreated_categoryIsCreatedWithSuccess(){ Category electronics = new Category("Electronics","Electronics","picture of various electronics.jpg"); Category subCategory1 = new Category("Cameras", "Cameras","Cameras"); subCategory1.setParent(electronics); Category subCategory2 = new Category("Smartphones", "Smartphones","Smartphones"); subCategory2.setParent(electronics); electronics.getChildren().add(subCategory1); electronics.getChildren().add(subCategory2); categoryRepository.save(electronics); }
Repository定义
@Repository public interface CategoryRepository extends JpaRepository<Category, Integer> { }
错误信息
org.springframework.dao.DataIntegrityViolationException: could not execute statement [ERROR: duplicate key value violates unique constraint "uk_81thrbnb8c08gua7tvqj7xdqk" Detail: Key (parent_id)=(20) already exists.] [insert into category (alias,enabled,image,name,parent_id) values (?,?,?,?,?)]; SQL [insert into category (alias,enabled,image,name,parent_id) values (?,?,?,?,?)]; constraint [uk_81thrbnb8c08gua7tvqj7xdqk] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:269) ...(省略栈跟踪)
问题原因
核心错误是错误使用了@OneToOne注解标记parent字段。
JPA中,@OneToOne关联会默认给外键字段(这里的parent_id)添加唯一约束,这意味着一个父分类只能对应一个子分类。当你尝试保存两个子分类并关联同一个父分类时,两个子分类的parent_id会被设置为同一个父分类ID,直接违反了parent_id的唯一约束,因此触发数据库错误。
级联操作(PERSIST/MERGE)负责的是实体的持久化/更新逻辑传递,它无法修改数据库层面的约束规则,所以根本无法阻止这个问题。
解决方案
将parent字段的@OneToOne注解替换为@ManyToOne,因为分类的父子关系本质是一对多(一个父分类可以有多个子分类,一个子分类只能属于一个父分类):
修改后的实体代码片段:
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }) @JoinColumn(name="parent_id") private Category parent;
同时,需要删除数据库中已存在的parent_id唯一约束(如果是自动建表,重新启动应用后Hibernate会自动生成正确的约束;如果是手动建表,需要执行SQL删除对应的唯一约束)。
这样修改后,多个子分类可以关联同一个父分类,不会再触发唯一约束冲突。
内容的提问来源于stack exchange,提问作者Teshte
相关产品推荐
相关产品推荐

