无法使用 Spring Data JPA / Hibernate 在事务中两次保存具有不可变集合的实体

Posted

技术标签:

【中文标题】无法使用 Spring Data JPA / Hibernate 在事务中两次保存具有不可变集合的实体【英文标题】:Can't save entity with immutable collection twice within Transaction using Spring Data JPA / Hibernate 【发布时间】:2021-08-24 23:31:29 【问题描述】:

我的 Spring Boot + Spring Data JPA 应用程序中有以下服务层功能:

@Service
public class MyService 

    @Autowired
    MyEntityRepository repository;

    @Transactional
    public void serviceMethod() 
        MyEntity newEntity = new MyEntity("code", "name", "description");
        newEntity = repository.save(newEntity); // --> all good here

        // ...

        newEntity.setName("updated name");
        newEntity = repository.save(newEntity); // --> all good here

        Set<ChildEntity> newChildrenEntities = Set.of(new ChildEntity("childName"));
        newEntity.setChildren(newChildrenEntities);
        newEntity = repository.save(newEntity); // --> Exception here!
        
        // ...
    

并且当执行到最后一个save方法时,抛出异常:

java.lang.UnsupportedOperationException: null
    at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:73) ~[na:na]
    at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.clear(ImmutableCollections.java:79) ~[na:na]
    at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:581) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.type.CollectionType.replace(CollectionType.java:757) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.type.TypeHelper.replace(TypeHelper.java:167) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
    at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:451) ~[hibernate-core-5.4.23.Final.jar:5.4.23.Final]
...

如果我切换操作的顺序,那么第二种方法也会引发异常:

@Transactional
public void serviceMethod() 
    MyEntity newEntity = new MyEntity("code", "name", "description");

    Set<ChildEntity> newChildrenEntities = Set.of(new ChildEntity("childName"));
    newEntity.setChildren(newChildrenEntities);
    newEntity = repository.save(newEntity); // --> all good here

    newEntity.setName("updated name");
    newEntity = repository.save(newEntity); // --> Exception here!

但是,如果我将 newChildrenEntities 实现更改为可修改的集合:

@Transactional
public void serviceMethod() 
    MyEntity newEntity = new MyEntity("code", "name", "description");
    newEntity = repository.save(newEntity); // --> all good here

    // ...

    newEntity.setName("updated name");
    newEntity = repository.save(newEntity); // --> all good here

    Set<ChildEntity> newChildrenEntities = new HashSet<>();
    newChildrenEntities.add(new ChildEntity("childName"));
    newEntity.setChildren(newChildrenEntities);
    newEntity = repository.save(newEntity); // --> all good here!
        
    // ...

或者如果我从我的方法中删除 @Transactional 注释,那么一切正常。

我想知道为什么会发生这种情况,这似乎与 Hibernate 如何处理集合有关。

【问题讨论】:

Set.of() 是一个固定长度的集合,hibernate 不允许这样做。也许可以查看 hibernate 提供的 @Immutable 注释。 【参考方案1】:

由于 Hibernate 在管理刷新/刷新/加载时需要控制/更改实体的状态,因此您会在自动刷新期间看到此异常。我建议您不要尝试将不可变集合用于实体映射。相反,您可以确保只向 API 用户公开不可变版本,但 Hibernate 可能需要更改内容。如果您认为这是一个错误或可以改进以支持您的用例,您还可以在问题跟踪器 (https://hibernate.atlassian.net) 中创建一个问题,并使用重现该问题的测试用例 (https://github.com/hibernate/hibernate-test-case-templates/blob/master/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java)。

【讨论】:

以上是关于无法使用 Spring Data JPA / Hibernate 在事务中两次保存具有不可变集合的实体的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot + Spring Data JPA + 事务无法正常工作

Spring Boot 自动配置无法与 spring-data-jpa 一起正常工作

无法使用 Spring Data JPA 执行聚合函数并获取结果

为什么Spring-Data-JPA Async无法正常工作?

Spring Data JPA + Postgres - 无法使用一对一映射插入数据

当加载 spring-boot 和 spring-data-jpa 时,Hibernate 无法加载 JPA 2.1 Converter