UnsupportedOperationException 与 hibernate 和 JPA 合并保存多对多关系

问题描述 投票:0回答:4

我已经使用 Hibernate 设置了一个简单的多对多关系帐户:角色,但是当我在添加角色后尝试在单元测试中保存帐户时,我收到 UnsupportedOperationException:

java.lang.UnsupportedOperationException
    at java.util.AbstractList.remove(AbstractList.java:144)
    at java.util.AbstractList$Itr.remove(AbstractList.java:360)
    at java.util.AbstractList.removeRange(AbstractList.java:559)
    at java.util.AbstractList.clear(AbstractList.java:217)
    at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:502)
    at org.hibernate.type.CollectionType.replace(CollectionType.java:582)
    at org.hibernate.type.TypeHelper.replace(TypeHelper.java:178)
    at org.hibernate.event.def.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:563)
    at org.hibernate.event.def.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:288)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:261)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84)
    at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855)
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:686)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
    at $Proxy33.merge(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:360)
    at ....JpaProvider.save(JpaProvider.java:161)
    at ....DataModelTest.testAccountRole(DataModelTest.java:47)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

这里出了什么问题?是我的实体设置有问题还是这是一个 hibernate 或 JPA 限制,迫使我将 m:m 关系分成 3 个 1:n 关系,对 m:n 关系表进行建模(我想避免这种情况,因为它没有任何附加信息)。我已经在我的原型中建模了其他 1:n 实体,到目前为止似乎效果很好......

这是我的设置,如果有任何想法是否有问题,我们将不胜感激。

实体(简化):

@Entity
@Table(name="account")
public class Account extends AbstractPersistable<Long> {

    private static final long serialVersionUID = 627519641892468240L;

    private String username;


    @ManyToMany
    @JoinTable( name = "account_roles", 
                joinColumns = { @JoinColumn(name = "account_id")}, 
                inverseJoinColumns={@JoinColumn(name="role_id")})  
    private List<Role> roles;   


    public List<Role> getRoles() {
        return roles;
    }
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }



    @Entity
    @Table(name="role")
    public class Role extends AbstractPersistable<Long> {

        private static final long serialVersionUID = 8127092070228048914L;

        private String name;

        @ManyToMany
        @JoinTable( name = "account_roles",   
                    joinColumns={@JoinColumn(name="role_id")},   
                    inverseJoinColumns={@JoinColumn(name="account_id")})  
        private List<Account> accounts;


        public List<Account> getAccounts() {
            return accounts;
        }

        public void setAccounts(List<Account> accounts) {
            this.accounts = accounts;
        }

单元测试:

@TransactionConfiguration
@ContextConfiguration({"classpath:dw-security-context-test.xml"})
@Transactional
@RunWith(SpringJUnit4ClassRunner.class)
public class DataModelTest {

    @Inject
    private AccountProvider accountProvider;    

    @Inject 
    private RoleProvider roleProvider;

    @Before
    public void mockAccountRolePermission(){
        Account account = MockAccount.getSavedInstance(accountProvider);
        Role role = MockRole.getSavedInstance(roleProvider);
    }

    @Test
    public void testAccountRole(){      
        Account returnedAccount = accountProvider.findAll().get(0);
        returnedAccount.setRoles(Arrays.asList(roleProvider.findAll().get(0)));
        accountProvider.save(returnedAccount);

    }
}

MockAccount(与 MockRole 相同):

public class MockAccount {

    public static Account getInstance(){
        Account account = new Account();
        account.setUsername(RandomData.rndStr("userName-", 5));
        return account;
    }

    public static Account getSavedInstance(AccountProvider accountProvider){
        Account account = getInstance();
        accountProvider.save(account);
        return account;
    }

}

最后是提供者:

@Repository
public class AccountProvider extends JpaProvider<Account, Long> {

}

其中 JPAProvider 只是包装了许多 JPARepository 方法(至少在本例中很重要):

public abstract class JpaProvider<T extends Object, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T>, QueryDslPredicateExecutor<T> {
...
}

关于为什么保存可能是不受支持的操作有什么想法吗?

hibernate jpa many-to-many
4个回答
134
投票

都是因为你

Arrays.asList(roleProvider.findAll().get(0))

这将创建一个不可修改的列表(实际上,一个不可调整大小的列表)。 Hibernate 似乎需要一个可修改的列表。尝试用这个代替:

public void testAccountRole(){      
    Account returnedAccount = accountProvider.findAll().get(0);

    List<Role> list = new ArrayList<Role>();
    list.add(roleProvider.findAll().get(0));    
    returnedAccount.setRoles(list);  

    accountProvider.save(returnedAccount);
}

这个解决方案不会解释为什么你会得到另一个异常(可能记录在 Hibernate 文档中),但它可能是一个有效的解决方法。


8
投票

Hibernate 的

Collection
的持久变体尝试委托给不实现
PersistenceBag
方法的抽象基类 (
add
)。


2
投票

奇怪的事情。我试图将我的问题包含在:

SubstanceEntity substance = this.substanceRepository.findById(id).get();
this.substanceRepository.save(substance);

并且错误仍然存在。 我发现这个问题与我的springboot测试有关。 在测试之外,如果我使用 List.of(...) 创建子实体并不重要,只要我不对 this 实例 进行更改,因为当我从数据库加载列表时,它会变得可变。 我认为在测试环境中,持久化的实体并未真正持久化,从我的存储库返回的部分实体仍然是不可变的。

最后我的解决方案是: 只是永远不要在我的测试中使用 List.of (或类似的不可变工厂)。


0
投票

我也遇到了这个问题,并且根据堆栈跟踪还推测问题是集合是不可变的。这是在看到这篇文章之前。 然而,我花了几个小时尝试各种解决方案,但无法解决异常。
这并不是说 Lukas Eder 接受的答案不正确,我的问题是 我试图保留的对象集在每个对象中也有集合(关系对象);因此,我必须使用可变集合重新初始化每个子集合,然后才能解决该异常。
但奇怪的是,在我的情况下,在生产代码中,这些子集合是不可变的,并且 saveAll() 工作得很好,但在我的 (JUnit) 测试上下文中,抛出了异常。

© www.soinside.com 2019 - 2024. All rights reserved.