Hibernate中Patch更新商品时关联Size数据重复问题
商品编辑时尺码重复问题
我有两张关联表Sizes和Goods,一个商品对应多个尺码。当发送请求编辑商品字段时,返回的尺码对象出现重复。
原始商品数据请求示例
{ "id": 10, "title": "Barnaul 2", "sex": "CHILD", "manufacturer": { "id": 1, "manufacturerName": "Ralf", "address": "Moscow", "telephone": "9-999-999-99-99" }, "manufacturerId": 1, "sizes": [ { "size": 39, "quantity": 4 }, { "size": 37, "quantity": 2 } ] }
错误结果示例
原本商品有37和39两个尺码,修改其中一个为40后,返回的尺码列表变成:
"sizes": [ { "size": 39, "quantity": 4 }, { "size": 37, "quantity": 2 }, { "size": 39, "quantity": 4 }, { "size": 40, "quantity": 2 } ]
相关代码
Good实体类
@Entity @Table(name = "goods") public class Good { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column(name = "Title") private String title; @Column(name = "sex") private String sex; @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REFRESH, CascadeType.MERGE}) @JoinColumn(name = "ManufacturerId", insertable = false, updatable = false) Manufacturer manufacturer; //多个商品对应一个厂商 @Column(name = "manufacturerId") private int manufacturerId; @JsonManagedReference @OneToMany(cascade = CascadeType.ALL, mappedBy = "good")//单向关联尺码数组 List<Size> sizes; //后续可修改CascadeType避免删除尺码时删除商品 public void addSizeToGood(Size size) { if (sizes == null) sizes = new ArrayList<>(); sizes.add(size); size.setGood(this); } //构造方法 public Good() { } public Good(String title, String sex, Manufacturer manufacturer, List<Size> sizes) { this.title = title; this.sex = sex; this.manufacturer = manufacturer; this.sizes = sizes; } }
Size实体类
@Entity @Table(name = "sizes") public class Size { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonIgnore private int id; @Column(name = "Size") private int size; @Column(name = "Quantity") private int quantity; @Column(name = "goodid", insertable = false, updatable = false) private int goodId; @JsonBackReference @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "goodId") Good good; //构造方法 public Size() { } public Size(int size, int quantity) { this.size = size; this.quantity = quantity; } }
DAO层代码
@Override public void saveGood(Good good) { Session session = entityManager.unwrap(Session.class); System.out.println(good); session.merge(good); //替代SaveOrUpdate }
控制器代码
@PatchMapping("/goods") public Good updateGood(@RequestBody Good good) { goodService.saveGood(good); return good; }
问题原因
核心问题在于session.merge(good)的执行逻辑:Hibernate会将传入的请求对象与Session缓存中的持久化商品对象合并。由于前端传入的尺码没有主键ID,Hibernate会将这些新尺码识别为待插入的新数据,而非更新原有尺码,最终导致新旧尺码共存。同时@OneToMany的CascadeType.ALL会触发级联持久化,进一步加剧重复问题。
解决方案
方案1:清空旧尺码后写入新尺码
修改DAO层的saveGood方法,先清除商品关联的所有旧尺码,再保存新的尺码列表:
@Override public void saveGood(Good good) { Session session = entityManager.unwrap(Session.class); // 加载数据库中持久化的商品对象 Good persistentGood = session.get(Good.class, good.getId()); // 清空旧尺码并立即执行删除操作 persistentGood.getSizes().clear(); session.flush(); // 绑定新尺码与商品的关联关系 List<Size> newSizes = good.getSizes(); if (newSizes != null) { newSizes.forEach(size -> size.setGood(persistentGood)); persistentGood.setSizes(newSizes); } // 更新商品的其他字段 persistentGood.setTitle(good.getTitle()); persistentGood.setSex(good.getSex()); persistentGood.setManufacturerId(good.getManufacturerId()); session.merge(persistentGood); }
方案2:基于标识匹配更新/新增尺码
让前端传递已有尺码的id(或用size值作为业务唯一键),在合并时判断是更新现有尺码还是新增:
@Override public void saveGood(Good good) { Session session = entityManager.unwrap(Session.class); Good persistentGood = session.get(Good.class, good.getId()); // 更新商品基础字段 persistentGood.setTitle(good.getTitle()); persistentGood.setSex(good.getSex()); persistentGood.setManufacturerId(good.getManufacturerId()); List<Size> existingSizes = persistentGood.getSizes(); List<Size> newSizes = good.getSizes(); if (newSizes != null) { // 处理更新:匹配已有尺码进行修改 for (Size newSize : newSizes) { boolean isExisting = false; for (Size existingSize : existingSizes) { // 优先用ID匹配,无ID则用size值匹配 if ((newSize.getId() != 0 && newSize.getId() == existingSize.getId()) || newSize.getSize() == existingSize.getSize()) { existingSize.setQuantity(newSize.getQuantity()); isExisting = true; break; } } // 非现有尺码则新增 if (!isExisting) { newSize.setGood(persistentGood); existingSizes.add(newSize); } } // 处理删除:移除前端未传递的原有尺码(按需开启) Iterator<Size> iterator = existingSizes.iterator(); while (iterator.hasNext()) { Size existingSize = iterator.next(); boolean isPresent = newSizes.stream() .anyMatch(newSize -> (newSize.getId() != 0 && newSize.getId() == existingSize.getId()) || newSize.getSize() == existingSize.getSize()); if (!isPresent) { iterator.remove(); } } } session.merge(persistentGood); }
额外优化建议
- 调整
Size实体的级联类型:将@ManyToOne(cascade = CascadeType.ALL)改为CascadeType.MERGE,避免不必要的级联操作。 - 前端请求时,尽量传递已有尺码的
id,让Hibernate能精准识别更新操作。
内容的提问来源于stack exchange,提问作者velovatik
相关产品推荐
相关产品推荐

