Hibernate在每个saveAndFlush上更新未更改的子行

时间:2017-07-07 20:44:59

标签: java sql hibernate

我有一个在Hiberate中具有一对多关系的数据库模型,如此

class ItemListBean{

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "list", cascade = CascadeType.ALL, orphanRemoval = true)
   List<ItemBean> items;

   ...
}

@Embeddable
class ListItemID{

   @ManyToOne
   @JoinColumn(name = "LIST_ID")
   ListItemBean list;

   @Column(name = "VALUE")
   String value;

   ...

}

...

class ItemBean{

   @EmbeddedId
   ListItemID id;

   //Other item properties, like creation date, created by...
}

在每次更新时,API都提供了所有项目的完整更新列表。每个ItemList保存数千个项目很常见,只有少数项目随每次更新而变化。

默认情况下,Hibernate会在任何更改时更新ItemList中的属性项,从而导致性能极差。

update LIST_ITEM set CREATE_DATE=?, CREATED_BY=? WHERE ITEM_VALUE=? and LIST_ID=?

我想只更新父列表的属性以及已更改的项目。给出API的完整新列表,以及数据库中的原始列表。我试图这样做:

    @Autowired
    JpaRepository<ListBean, Long> listDAO;

    @Autowired
    JpaRepository<ListItemBean, ListItemID> itemDAO;

...

    ListChangeset listDiff = new ListChangeset(updatedList, originalList);

    listDiff.computeItemsToDelete().foreach(li -> itemDAO.delete(li.getID());
    listDiff.computeItemsToAdd().foreach(li -> itemDAO.create(li));

    //Ensure the properties of the unchanged items in the updated list are exactly the same as the properties in the original list
    listDiff.computeRetainedItems().foreach(li -> applyItemProperties(li, originalList));

    return listDAO.saveAndFlush(updatedList);

我可以看到更改集是正确计算的,未修改的ItemBeans与Java完全相同......但是hibernate仍会更新最终saveAndFlush上的每个列表项。

如何阻止它这样做?

====

为了完整性,请参阅此处的ListChangeset

  private final Collection<ListItemBean> updatedList;
  private final Collection<ListItemBean> originalList;

  ListChangeset(Collection<ListItemBean> updatedList, Collection<ListItemBean> originalList) {
    this.updatedList = updatedList;
    this.originalList = originalList;
  }

  Collection<ListItemID> computeItemsToDelete(ListBean parent) {
    Collection<ListItemBean> itemsToDelete = removeAll(originalList, updatedList);
    return itemsToDelete.stream().map(li -> new ListItemID(parent, li.getValue())).collect(Collectors.toSet());
  }

  Collection<ListItemBean> computeItemsToAdd() {
    return removeAll(updatedList, originalList);
  }

  private static Collection<ListItemBean> removeAll(Collection<ListItemBean> collection, Collection<ListItemBean> remove) {
    List<ListItemBean> itemsToAdd = new ArrayList<>();

    Set<String> removeSet = remove.stream().map(li -> li.getValue()).collect(Collectors.toCollection(HashSet::new));
    for (ListItemBean item : collection){
      if(!removeSet.contains(item.getValue())){
        itemsToAdd.add(item);
      }
    }

    return itemsToAdd;
  }

  public Collection<ListItemBean> computeRetainedItems() {
    List<ListItemBean> retainedItems = new ArrayList<>();

    Set<String> initial = originalList.stream().map(li -> li.getValue()).collect(Collectors.toCollection(HashSet::new));
    for (ListItemBean itemBean : updatedList) {
      if(initial.contains(itemBean.getValue())){
        retainedItems.add(itemBean);
      }
    }

    return retainedItems;
  }

1 个答案:

答案 0 :(得分:0)

此行为是由于updatedList项集合是Java集合,而originalList项集合是Hibernate collection proxy。< / p>

当我们saveAndFlush(updatedList)时,现有的集合代理将被Java集合替换。 Hibernate丢失了对现有数据库对象的引用,因此它更新了数据库中的整个集合。

此处的解决方案是修改并保存originalList及其项目 - 添加和删除已更改的项目到现有集合。这告诉Hibernate项目增量是什么,并允许它只将更改的项目写入数据库。

工作代码如下所示

// Set the changed properties of the new list in the existing one
originalList.setName(updatedList.getName());
originalList.setDescription(updatedList.getDescription());

...

// Compute the changed items and set them in the original list
ListChangeset listDiff = new ListChangeset(updatedList, originalList);

originalList.getItems().removeAll(listDiff.computeItemsToDelete());
originalList.getItems().addAll(listDiff.computeItemsToAdd());

return listDAO.saveAndFlush(originalList);