web-dev-qa-db-ja.com

UnsupportedOperationExceptionマージ-HibernateおよびJPAとの多対多の関係の保存

単純な多対多の関係アカウント: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)

ここで何が問題になっていますか?エンティティのセットアップに問題がありますか、それとも休止状態または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> {
...
}

保存がUnsupportedOperationである理由についてのアイデアはありますか?

39
Pete

それはあなたのせいです

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のドキュメントに記載されている場合があります)が、有効な回避策である可能性があります。

70
Lukas Eder

問題のCollectionのHibernateの永続的なバリアントは、PersistenceBagメソッドを実装しない抽象基本クラス(add)に委任しようとします。

4
Sakshi Sachdeva