我有一个在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;
}
答案 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);