当实体集合被替换为Java 21的不可变List时,Hibernate的 merge会抛出UnsupportedOperationException
背景
- Spring Boot: 4.0.2
- Hibernate: 7.2.1.Final
- Spring Data JPA: 4.0.2
- Java: 21
- PostgreSQL
我有一个 ServiceItem 实体和一个 @OneToMany 集合:
@OneToMany(mappedBy = "serviceItem",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<ServiceItemPricing> pricingList = new ArrayList<>();
public void setPricingList(List<ServiceItemPricing> pricingList) {
this.pricingList = pricingList;
}
代码
@Transactional
public ServiceItemDto createServiceItem(CreateServiceItemDto dto) {
ServiceItem serviceItem = new ServiceItem(...);
// After the first save, Hibernate reports the collection as PersistentBag
ServiceItem saved = serviceItemRepository.save(serviceItem);
List<ServiceItemPricing> pricingList =
dto.getData()
.stream()
.map(data ->
new ServiceItemPricing(
saved,
category,
data.getPrice(),
data.getDurationMinutes()
))
.toList(); // Java 21 immutable list
saved.setPricingList(pricingList);
// Second save triggers the exception
serviceItemRepository.save(saved);
return new ServiceItemDto(saved);
}
异常
java.lang.UnsupportedOperationException
at java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.util.ImmutableCollections$AbstractImmutableCollection.clear(ImmutableCollections.java:149)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:480)
at org.hibernate.type.CollectionType.replaceOriginal(CollectionType.java:675)
at org.hibernate.type.CollectionType.replace(CollectionType.java:638)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(...)
随后我逐步查看了Hibernate的源代码。
在合并过程中我观察到:
replaceElements(original, target)
original = ImmutableCollections$ListN
target = PersistentBag
在 replaceElements() 里:
final var result = (Collection) target;
result.clear();
这最终会到达:
PersistentBag.clear()
随后调用了:
collection.clear();
调用最终到达:
ImmutableCollections$AbstractImmutableCollection.clear()
并抛出异常。
问题
我理解在一个事务中第二个 save() 是多余的,但在研究Hibernate的合并行为时,我特意把它保留在原地。为什么Hibernate会对分配给不可变列表的i 调用clear() 方法?clear应该作用于ArrayList<>(),因为它是默认的初始值。
我想理解Hibernate的内部合并过程,而不仅仅是绕开这个问题。
解决方案
你可以使用 Stream.collect(...) 来创建一个可变的 ArrayList,而不是 Stream.toList()
例如:
List<ServiceItemPricing> pricingList = dto.getData()
.stream()
.map(...)
.collect(Collectors.toCollection(ArrayList::new));
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。