Spring Boot更新产品触发java.lang.UnsupportedOperationException问题求助
Spring Boot产品更新时UnsupportedOperationException异常排查与解决
问题场景
Spring Boot开发中,新增产品功能正常,但调用save方法执行更新操作时抛出java.lang.UnsupportedOperationException,已确认产品ID存在,使用环境为Java 17、Spring Framework 5.3.18。
相关代码
1. ProductService的save方法实现
@Override public ProductDTO save(ProductDTO product) { ProductEntity entity= null; if(product.getId()!=null) { ProductEntity oldProduct= productrepository.findById(product.getId()).get(); entity=converter.toEntity(product, oldProduct); }else { entity= converter.modelMapper().map(product, ProductEntity.class); } entity.setCategory(categoryRepository.findById(product.getCategoryId()).get()); entity.setOrigins(toList(originRepository.findById(product.getOriginId()))); entity.setMaterials(toList(materialRepository.findById(product.getMaterialId()))); entity.setType(typeRepository.findById(product.getTypeId()).get()); productrepository.save(entity); return converter.modelMapper().map(entity, ProductDTO.class); }
2. 新增与更新产品API接口
@PostMapping(value="/product") public ProductDTO addProduct(@ModelAttribute ProductDTO model) { return iProductService.save(model); } @PutMapping(value="/product") public ProductDTO editProduct(@ModelAttribute ProductDTO model) { return iProductService.save(model); }
异常栈信息
2022-09-08 20:19:40.892 ERROR 14524 --- [nio-8081-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.UnsupportedOperationException] with root cause java.lang.UnsupportedOperationException: null at java.base/java.util.AbstractList.remove(AbstractList.java:167) ~[na:na] at java.base/java.util.AbstractList$Itr.remove(AbstractList.java:387) ~[na:na] at java.base/java.util.AbstractList.removeRange(AbstractList.java:598) ~[na:na] at java.base/java.util.AbstractList.clear(AbstractList.java:243) ~[na:na] at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:580) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.type.CollectionType.replace(CollectionType.java:757) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.type.TypeHelper.replace(TypeHelper.java:167) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:450) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:205) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:178) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:829) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:816) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362) ~[spring-orm-5.3.18.jar:5.3.18] at jdk.proxy25/jdk.proxy25.$Proxy348.merge(Unknown Source) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311) ~[spring-orm-5.3.18.jar:5.3.18] at jdk.proxy25/jdk.proxy25.$Proxy348.merge(Unknown Source) ~[na:na] at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:640) ~[spring-data-jpa-2.6.3.jar:2.6.3] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) ~[spring-data-commons-2.6.3.jar:2.6.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.18.jar:5.3.18] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.18.jar:5.3.18] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.18.jar:5.3.18] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.18.jar:5.3.18] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174) ~[spring-data-jpa-2.6.3.jar:2.6.3] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.18.jar:5.3.18] at jdk.proxy25/jdk.proxy25.$Proxy350.save(Unknown Source) ~[na:na] at com.lifood.stuneed.service.impl.ProductService.save(ProductService.java:81) ~[classes/:na] at com.lifood.stuneed.api.ProductAPI.addProduct(ProductAPI.java:46) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
问题排查与解决方案
核心原因
异常栈显示Hibernate在清理集合时调用AbstractList.clear()抛出异常,说明**entity.setOrigins()或entity.setMaterials()设置的集合是不可修改的**。
大概率是toList方法返回了Arrays.asList()生成的集合,或是Spring Data JPA查询返回的不可修改集合,这类集合不支持增删改操作;新增时实体是瞬时态,Hibernate会直接创建新集合所以无问题,更新时实体是持久态,Hibernate需要修改现有集合,因此触发异常。
解决方案
方案1:修改toList方法返回可修改集合
private <T> List<T> toList(Optional<T> optional) { List<T> list = new ArrayList<>(); optional.ifPresent(list::add); return list; }
方案2:在调用处直接转换为可修改集合
如果不想修改toList方法,可在设置集合时转换:
entity.setOrigins(new ArrayList<>(toList(originRepository.findById(product.getOriginId())))); entity.setMaterials(new ArrayList<>(toList(materialRepository.findById(product.getMaterialId()))));
内容的提问来源于stack exchange,提问作者stupidlifood
相关产品推荐
相关产品推荐

