当实体集合被替换为Java 21的不可变List时,Hibernate的 merge会抛出UnsupportedOperationException

后端开发 2026-07-08

背景

  • 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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章