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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:48:07