Spring Boot中使用JPA删除OneToMany关联的子实体(歌曲)问题
问题根因
你的删除操作失败是以下4个核心原因共同导致的:
Song类未重写equals和hashCode方法:ArrayList.remove()依赖equals判断对象是否匹配,你请求体反序列化得到的Song是全新对象,和持久化上下文里加载的Song对象内存地址不同,默认的equals判定为不匹配,所以移除操作根本没生效。- 双向关联未完整维护:你现有的
removeSong方法只将歌曲从播放列表的集合中移除,没有把对应Song实例的playList属性置空,JPA不会识别这种不完整的关联变更。 - 删除操作后未保存变更:删除接口中调用
removeSong后没有执行savePlayList操作,就算集合变更成功也不会同步到数据库。 - 请求参数缺少唯一标识:你传入的删除请求体没有携带数据库生成的歌曲ID,即便重写
equals用业务字段匹配,也可能出现重复歌曲误删的问题。
修复步骤
1. 重写Song类的equals和hashCode方法
建议用主键ID作为判断依据,也可以根据业务需求用title+artist+album+year的组合唯一键:
// Song类中添加 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Song song = (Song) o; return id != null && id.equals(song.id); } @Override public int hashCode() { return getClass().hashCode(); }
2. 完善PlayList的removeSong方法
维护双向关联的两端:
@Transient public void removeSong(Song song) { songs.remove(song); song.setPlayList(null); }
3. 修改删除接口,补充保存逻辑,建议调整参数传递方式
优先推荐用路径参数传递歌曲ID,避免请求体匹配的问题,修改后的接口如下:
// 调整为路径传递播放列表ID和歌曲ID @DeleteMapping("/delete/{playlistId}/song/{songId}") public PlayList deleteSongFromPlayList(@PathVariable(value = "playlistId") Long playlistId, @PathVariable(value = "songId") Long songId){ PlayList playList = playListService.getById(playlistId).orElseThrow(() -> new RuntimeException("播放列表不存在")); // 从播放列表的歌曲集合中匹配对应ID的歌曲 Song toRemove = playList.getSongs().stream() .filter(song -> songId.equals(song.getId())) .findFirst() .orElseThrow(() -> new RuntimeException("该播放列表下无对应歌曲")); playList.removeSong(toRemove); // 保存变更同步到数据库 playListService.savePlayList(playList); return playList; }
如果一定要用请求体传递歌曲信息,需要保证请求体携带歌曲ID,或者把equals方法的判断逻辑修改为业务字段匹配。
可选调整
如果你希望删除播放列表中的歌曲时,只解除关联不删除SONG表中的对应记录,需要把@OneToMany注解中的orphanRemoval = true配置删除。
内容的提问来源于stack exchange,提问作者Cesar Justo
相关产品推荐
相关产品推荐

