Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnsupportedOperationException when merging an existing Hibernate model object?

After an upgrade from Hibernate 3 to 4, we're working through a few kinks that popped up along the way. One that has us particularly stumped is an UnsupportedOperationException, where an existing object is pulled from the database, tweaked, and merged.

The problem is that Hibernate appears to be adding an object to an AbstractList

This only seems to happen to one particular object type, when saved in our DAO, but as best as we can tell:

  1. We're not using any sublist() or asList() methods that would cause an immutable instance to be created.
  2. Examining the object that's being saved (which is enormous and has many children) I don't think that any of its children items are AbstractList types.

Here are the code snippets around the stack points:

HibernateDao.save():

@Transactional
public void save(T item) {
    try {
        getSessionFactory().getCurrentSession().merge(item);
    } catch (Exception ex) {
        LOGGER.debug("Unable to merge", ex);
        LOGGER.warn("Unable to merge item, saving instead. (Of type " + item.getClass() + ")");
        getSessionFactory().getCurrentSession().saveOrUpdate(item);
    }
}

Our User item, which is being saved, has a number of children items defined like so:

@OneToMany(cascade = CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private Map<String, Project> associatedProjects = new HashMap<String, Project>();

The Project class has other similarly-annotated children, but everything has CascadeType.ALL, and LazyCollectionOption.FALSE defined.

Here is the (quite tall) stack trace:

Note that our code begins with com.company.application

06/04 18:15:45 DEBUG [Thread-19258] hibernate.HibernateDao.save- Unable to merge
java.lang.UnsupportedOperationException
        at java.util.AbstractList.add(AbstractList.java:148)
        at java.util.AbstractList.add(AbstractList.java:108)
        at org.hibernate.collection.internal.PersistentBag.add(PersistentBag.java:292)
        at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:496)
        at org.hibernate.type.CollectionType.replace(CollectionType.java:563)
        at org.hibernate.type.AbstractType.replace(AbstractType.java:178)
        at org.hibernate.type.TypeHelper.replaceAssociations(TypeHelper.java:261)
        at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:398)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:221)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:282)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
        at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:409)
        at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:350)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
        at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
        at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:409)
        at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:350)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
        at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
        at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
        at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:409)
        at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:350)
        at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
        at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
        at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
        at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892)
        at com.company.hibernate.HibernateDao.save(HibernateDao.java:129)
        at sun.reflect.GeneratedMethodAccessor62.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
        at $Proxy53.save(Unknown Source)
        at com.company.application.UserManager.save(UserManager.java:46)
        at sun.reflect.GeneratedMethodAccessor67.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
        at $Proxy66.save(Unknown Source)
        at com.company.application.UserOperationController.saveUser(UserOperationController.java:533)

We're not sure where the AbstractList is coming from, or how we're responsible. Are there any potential pitfalls when working with Hibernate 4 (this issue is new since the upgrade) that could result in partially-unmodifiable objects? Or that would cause Hibernate to act in a way that results in it attempting to create unmodifiable instances of objects?

like image 407
Craig Otis Avatar asked Jun 04 '12 22:06

Craig Otis


3 Answers

I ran into this same issue (with Sets not Lists) and it seems like Hibernate's Persistent variant of the collection in question attempts to delegate to an abstract base class that doesn't implement the add method. The solution I found was to set the data member to null, do a merge and then set the data member back to the new list that I wanted it to contain. The rather obvious downside is that I blow away all of the records even if I only want to add or remove one. The upside is that it actually works...

like image 165
Chris Thompson Avatar answered Nov 19 '22 13:11

Chris Thompson


In this block of code:

@OneToMany(cascade = CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private Map<String, Project> associatedProjects = new HashMap<String, Project>();

how about to add this.

@OneToMany(cascade = CascadeType.ALL)

*****@JoinColumn(name="")***** 

@LazyCollection(LazyCollectionOption.FALSE)
private Map<String, Project> associatedProjects = new HashMap<String, Project>();
like image 1
Myo Avatar answered Nov 19 '22 13:11

Myo


I had the same effect for recently - I had used java.util.Arrays#asList in my test case.

It was easy to miss, as the returned class is named ArrayList, but it was not java.util.ArrayList, but java.util.Arrays.ArrayList

like image 1
RobertG Avatar answered Nov 19 '22 14:11

RobertG