Hibernate One to Many Mapping保存评论时后3条覆盖前3条问题
问题根因
这个异常本质是JPA实体关联的基础特性和代码逻辑冲突导致的:
- Comment表的
post_id是单值外键,单条评论记录永远只能关联一个Post,不可能同时归属两个不同的帖子 - 代码里只创建了3个Comments持久化实例,并且把这同一个集合的实例先后绑定给了post2和post1两个不同的Post对象
执行流程对应数据库操作如下:
- 保存post1时,post1没有绑定评论,仅插入post表记录
- 保存post2时,绑定的3个Comments实例会被插入Comment表,外键
post_id指向post2的主键,此时库中共有3条评论,全关联post2 - 给post1绑定同一个Comments集合并调用save时,JPA检测到3个已经持久化的Comments实例的关联Post从post2变更为post1,会自动执行UPDATE语句,把这3条评论的
post_id外键全部改成post1的主键
你以为会插入6条评论,实际上从头到尾只有3条评论实体,只是它们的外键被更新了,自然会出现“后3条覆盖前3条外键”的现象。
修复方案
根据实际业务需求二选一即可:
- 如果你需要post1、post2各持有3条独立的评论,总共6条评论记录
不要复用已经和post2绑定的Comments实例集合,给post1绑定评论时必须新建3个独立的Comments对象,修正代码如下:
// 给post2准备的3条评论 Comments comments = new Comments("1st Comment", new Date(System.currentTimeMillis())); Comments comments2 = new Comments("2st Comment", new Date(System.currentTimeMillis())); Comments comments3 = new Comments("3st Comment", new Date(System.currentTimeMillis())); ArrayList<Comments> post2Comments = new ArrayList<>(); post2Comments.add(comments); post2Comments.add(comments2); post2Comments.add(comments3); // 插入无评论的post1 Post post1 = new Post("1st Post", "1st Post Description", new ArrayList<Comments>(), new Date(System.currentTimeMillis())); postReposetory.save(post1); // 插入带3条评论的post2 Post post2 = new Post("2st Post", "2st Post Description", post2Comments, new Date(System.currentTimeMillis())); postReposetory.save(post2); // 给post1准备3条全新的独立评论实例,不要复用之前的对象 Comments post1C1 = new Comments("1st Comment", new Date(System.currentTimeMillis())); Comments post1C2 = new Comments("2st Comment", new Date(System.currentTimeMillis())); Comments post1C3 = new Comments("3st Comment", new Date(System.currentTimeMillis())); ArrayList<Comments> post1Comments = new ArrayList<>(); post1Comments.add(post1C1); post1Comments.add(post1C2); post1Comments.add(post1C3); // 更新post1,绑定新评论 post1.setComments(post1Comments); post1.setUpdatedAt(new Date(System.currentTimeMillis())); postReposetory.save(post1);
- 如果你的业务需求就是把原本属于post2的3条评论,转移到post1下
那现有代码的执行结果就是JPA的正常行为,不存在异常。如果要避免后续开发中出现意外的关联覆盖,检查Post类上@OneToMany注解的级联配置,不要无脑配置CascadeType.ALL,根据实际需要收窄级联操作范围即可,比如仅保留CascadeType.PERSIST、CascadeType.MERGE,移除不需要的级联更新/删除规则。
内容的提问来源于stack exchange,提问作者Ashutosh
相关产品推荐
相关产品推荐

