JPA更新多对多关联实体时重复创建对象及关联数据问题求助
问题根因
你当前的更新逻辑存在4个核心问题:
- 每次处理DTO时直接
new WorksiteDevice后调用保存接口,没有匹配当前工地已有的关联记录,所有操作都会触发新增 - DTO中定义的
id字段完全没有被使用,后端无法区分是修改已有记录还是新增记录 - 修改设备名称时直接用新名称查询/创建设备,直接关联新的Device对象到新增的WorksiteDevice,没有处理原有关联记录的更新
- 实体类、DTO存在多处低级语法错误,导致属性赋值/读取异常
修复步骤
第一步:修复基础语法错误
修复Device实体的get/set方法
@Entity @Table(name = "device") public class Device { // 成员变量统一小驼峰命名 private long id; private String deviceName; private Set<WorksiteDevice> worksiteDevices = new HashSet<>(); @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) @Column(name = "device_id") public long getId() { return id; } public void setId(long id) { this.id = id; } // 修正返回值错误 public String getDeviceName() { return deviceName; } // 修正赋值逻辑 public void setDeviceName(String deviceName) { this.deviceName = deviceName; } // 其余方法保持不变 }
修复WorksiteDevice实体的冗余定义
@Entity @Table(name = "worksite_device") public class WorksiteDevice { private long id; private Worksite worksite; private Device device; private Integer deviceCount; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) @Column(name = "worksite_device_id") public long getId() { return id; } public void setId(long id) { this.id = id; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "worksite_id") public Worksite getWorksite() { return worksite; } public void setWorksite(Worksite worksite) { this.worksite = worksite; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "device_id") // 补全device的get方法 public Device getDevice() { return device; } public void setDevice(Device device) { this.device = device; } // 其余方法保持不变 }
修复WorksiteDeviceDTO的赋值逻辑
public void setDeviceName(String deviceName) { // 加this关键字,避免赋值给局部变量 this.deviceName = deviceName; }
第二步:重构更新业务逻辑
你已经配置了cascade = CascadeType.ALL和orphanRemoval = true,直接通过Worksite的关联集合操作即可,不需要单独调用WorksiteDevice的保存接口:
// 保存工地基础信息 worksiteService.saveWorksite(worksite); // 构造当前工地已有关联的id->实体映射 Map<Long, WorksiteDevice> existRelMap = worksite.getWorksiteDevices().stream() .collect(Collectors.toMap(WorksiteDevice::getId, Function.identity())); Set<WorksiteDevice> newRelSet = new HashSet<>(); // 遍历处理所有前端传入的DTO for (WorksiteDeviceDTO dto : worksiteDeviceDtos) { WorksiteDevice rel; // 携带id且匹配到已有记录,走更新逻辑 if (dto.getId() != null && existRelMap.containsKey(dto.getId())) { rel = existRelMap.get(dto.getId()); existRelMap.remove(dto.getId()); } else { // 无匹配记录,走新增逻辑 rel = new WorksiteDevice(); rel.setWorksite(worksite); } // 处理设备关联 Device device = getOrSaveDeviceByName(dto.getDeviceName()); rel.setDevice(device); rel.setDeviceCount(dto.getDeviceCount()); newRelSet.add(rel); } // existRelMap剩余的记录是前端删除的关联,自动触发orphanRemoval删除 // 更新工地的关联集合 worksite.setWorksiteDevices(newRelSet); // 级联保存所有变更 worksiteService.saveWorksite(worksite);
第三步:调整设备查询方法的事务传播级别
保证该方法在父事务中运行,避免出现游离态对象导致重复入库:
@Transactional(propagation = Propagation.MANDATORY) public Device getOrSaveDeviceByName(String deviceName) { return deviceNameService.findByName(deviceName) .orElseGet(() -> deviceService.saveNewDeviceWithName(deviceName)); }
额外注意
如果你的业务场景是修改原有Device的名称,而非替换关联的Device,需要额外增加名称修改逻辑,不能直接用新名称查询创建:
// 处理已有关联的设备名称变更 Device oldDevice = rel.getDevice(); if (!oldDevice.getDeviceName().equals(dto.getDeviceName())) { Optional<Device> newDeviceOpt = deviceNameService.findByName(dto.getDeviceName()); if (newDeviceOpt.isPresent()) { // 新名称已存在,直接替换关联 rel.setDevice(newDeviceOpt.get()); } else { // 新名称不存在,修改原有设备的名称 oldDevice.setDeviceName(dto.getDeviceName()); deviceService.save(oldDevice); } }
前端修改关联时必须携带原有WorksiteDevice的id,否则后端无法区分新增和修改场景。
内容的提问来源于stack exchange,提问作者fab_95
相关产品推荐
相关产品推荐

