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

一对一关联无法删除子实体及重复创建问题排查

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 14:17:49