一对一关联无法删除子实体及重复创建问题排查
JPA OneToOne关联问题:删除时报错、覆盖时重复插入
问题1:设置state为null时报错的原因
你的PublicProfileDancer与State是双向OneToOne关联,其中:
PublicProfileDancer.state是反向端(通过mappedBy指定关联由State.publicProfileDancer维护)- 你给反向端的关联添加了
cascade = CascadeType.ALL, orphanRemoval = true
当执行publicProfile.setState(null)时,orphanRemoval=true会标记原State实体为删除,但此时原State仍持有对PublicProfileDancer的引用。JPA会判定这个已被标记删除的实体又被关联到持久化上下文,因此抛出EntityNotFoundException提示“被删除的对象会被级联重新保存”。
问题2:覆盖state时生成两条记录的原因
每次调用stateMapper.map()都会创建新的State实体,并设置其publicProfileDancer引用指向当前PublicProfileDancer。但原State实体的关联引用并未被清空,且反向端的级联规则不会自动删除原State,最终导致原记录保留、新记录插入,数据库出现两条State数据。
修复方案
1. 调整级联与孤儿移除的配置位置
双向OneToOne关联中,级联操作(含orphanRemoval)应设置在主控端(持有外键的一方),也就是State实体的关联字段上。
修改PublicProfileDancer实体:
@Entity @Table( name = "publicProfileDancer" ) @Data public class PublicProfileDancer { @Id @GeneratedValue( strategy = GenerationType.AUTO ) private Long id; // 移除反向端的cascade和orphanRemoval @OneToOne( mappedBy = "publicProfileDancer", fetch = FetchType.LAZY ) private State state; ... }
修改State实体:
@Entity @Table( name = "state" ) @Data @NoArgsConstructor public class State { @Id @GeneratedValue public Long id; @Basic @Column( name = "statesID" ) private Long statesID; @Basic @Column( name = "name" ) private String name; // 将级联和orphanRemoval移至主控端 @OneToOne( fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true ) @JoinColumn( name = "publicProfileDancer_id" ) private PublicProfileDancer publicProfileDancer; }
2. 实现双向关联的同步更新
直接用Lombok自动生成的setter无法处理双向关联的同步,需要给PublicProfileDancer自定义setState方法,确保两端引用同步:
@Entity @Table( name = "publicProfileDancer" ) @Data public class PublicProfileDancer { // ... 其他字段 @Setter(AccessLevel.NONE) // 禁止Lombok生成默认setter @OneToOne( mappedBy = "publicProfileDancer", fetch = FetchType.LAZY ) private State state; public void setState(State state) { // 清空原state的关联 if (this.state != null) { this.state.setPublicProfileDancer(null); } this.state = state; // 设置新state的关联 if (state != null) { state.setPublicProfileDancer(this); } } }
3. 修改Mapper与业务代码,避免重复创建实体
修改stateMapper.map()方法,支持更新现有实体而非每次新建:
public State map(StateDTO stateDTO, State existingState, PublicProfileDancer publicDancer) { if (stateDTO == null) { if (existingState != null) { existingState.setPublicProfileDancer(null); } return null; } // 优先使用现有实体,无实体则新建 State stateEntity = existingState != null ? existingState : new State(); stateEntity.setStatesID(stateDTO.getStatesID()); stateEntity.setName(stateDTO.getName()); stateEntity.setCountryId(stateDTO.getCountryId()); stateEntity.setCountryName(stateDTO.getCountryName()); stateEntity.setPublicProfileDancer(publicDancer); return stateEntity; }
调整业务代码的调用逻辑:
@Transactional // 注意注解拼写为@Transactional public User mapPublicUser(PublicUserDTO publicUserDTO, User userEntity) { if (publicUserDTO == null) return null; PublicProfileDancer publicProfile; if(userEntity.getPublicProfileDancer() != null){ publicProfile = userEntity.getPublicProfileDancer(); }else{ publicProfile = new PublicProfileDancer(); publicProfile.setUser(userEntity); userEntity.setPublicProfileDancer(publicProfile); } // 传入现有state,实现更新或新建 State updatedState = stateMapper.map(publicUserDTO.getState(), publicProfile.getState(), publicProfile); publicProfile.setState(updatedState); publicProfile.setValue1("Anthing else"); ... // 其他字段设置 return userEntity; }
内容的提问来源于stack exchange,提问作者Paul
相关产品推荐
相关产品推荐

