I have two PostgreSQL tables: one for Product and another for gallery images with SEO details. Here are the schemas:
create table if not exists test_product
(
id bigserial not null,
version bigserial not null,
name text not null,
sku varchar(50) not null,
primary key (id)
);
create table if not exists test_product_image_gallery_seo
(
id bigserial not null,
version bigserial not null,
product_id bigint not null,
image text not null,
alt_text text not null,
primary key (id),
constraint fk_product_id foreign key (product_id) references test_product (id),
constraint test_unique_seo unique (product_id, image)
);
In my Spring Boot application, I have two Hibernate ORM entities representing these tables. Below are their definitions:
TestProduct
@Entity(name = "test_product")
@Table(name = "test_product")
public class TestProduct extends BehemothORM {
@Column(
name = ProductContract.NAME,
nullable = false,
unique = true,
columnDefinition = "TEXT"
)
private String name;
@Column(
name = ProductContract.SKU,
nullable = false,
unique = true,
columnDefinition = "VARCHAR"
)
private String sku;
@OneToMany(
mappedBy = "product",
fetch = FetchType.EAGER,
targetEntity = TestProductImageGallerySEO.class,
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<TestProductImageGallerySEO> imageGallerySEOList;
}
TestProductGalleryImageSEO
@Entity(name = "test_product_gallery_image_seo")
@Table(
name = "test_product_gallery_image_seo",
uniqueConstraints = {
@UniqueConstraint(
name = "test_unique_seo",
columnNames = {
ProductImageGallerySEOContract.PRODUCT_ID,
ProductImageGallerySEOContract.IMAGE
}
)
}
)
public class TestProductImageGallerySEO extends BehemothORM {
@Column(
name = "image",
columnDefinition = "TEXT",
nullable = false
)
private String image;
@Column(
name = "alt_text",
columnDefinition = "TEXT",
nullable = false
)
private String altText;
@ToString.Exclude
@ManyToOne(
targetEntity = TestProduct.class,
fetch = FetchType.EAGER
)
@JoinColumn(
name = ProductImageGallerySEOContract.PRODUCT_ID,
columnDefinition = "BIGSERIAL",
nullable = false,
foreignKey = @ForeignKey(
name = "fk_product_id"
)
)
@GsonExclude
private TestProduct product;
@Transient
private boolean deleted = false;
}
Here's the logic where I add or update gallery images for a product:
@Transactional()
public int modifyGalleryImages(@NotNull TestProductImageGallerySEOPayload seoPayload) {
// gets the current product as entity from database
TestProduct product = this.testProductDAOController.retrieveEntity(seoPayload.getProductId());
for (TestProductImageGallerySEO imageGallerySEO : seoPayload.getGallerySEOList())
{
imageGallerySEO.setProduct(product);
this.addNewEntity(imageGallerySEO);
}
return ActionCode.UPDATE_SUCCESS;
}
This code was working previously. However, after upgrading Hibernate versions this exact code is failing without any changes made.
After upgrading Hibernate from version 6.3.1
to 6.6.3
, I'm getting the following error:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bloomscorp.loom.seo.orm.TestProductImageGallerySEO#0]
This error occurs when trying to save a new TestProductImageGallerySEO
entity using addNewEntity
. Here is the seoPayload
{
"productId": 37503,
"gallerySEOList": [
{
"id": 0,
"version": 0,
"image": "uvw.jpg",
"altText": "",
"deleted": false
},
{
"id": 0,
"version": 0,
"image": "https://abc.jpg",
"altText": "",
"deleted": false
},
{
"id": 0,
"version": 0,
"image": "https://xyz.jpg",
"altText": "",
"deleted": false
}
]
}
Additional Context:
if anybody wants to know what BehemothORM
and addNewEntity
does, these are basically our own set of libraries that we use to speed up the development process. Here is the defination of these two
BehemothORM
BehemothORM
is a base class with common fields like id
and version
addNewEntity
addNewEntity
is a wrapper around JpaRepository.save()
Questions:
Thanks in advance!
Edit
As requested by @M.Deinum here is the complete stacktrace
jakarta.servlet.ServletException: Request processing failed: org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bloomscorp.loom.seo.orm.TestProductImageGallerySEO#0]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1022)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at com.bloomscorp.nverse.NVerseRequestFilter.doFilterInternal(NVerseRequestFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at com.bloomscorp.nverse.NVerseExceptionHandlerFilter.doFilterInternal(NVerseExceptionHandlerFilter.java:58)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at com.bloomscorp.nverse.NVerseHttpRequestFilter.doFilterInternal(NVerseHttpRequestFilter.java:20)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:219)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bloomscorp.loom.seo.orm.TestProductImageGallerySEO#0]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:304)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:232)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:243)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244)
at jdk.proxy2/jdk.proxy2.$Proxy265.save(Unknown Source)
at com.bloomscorp.behemoth.dao.controller.AbstractAddEntityDAOController.addNewEntity(AbstractAddEntityDAOController.java:22)
at com.bloomscorp.loom.seo.dao.controller.TestProductImageGallerySEODAOController.modifyGalleryImages(TestProductImageGallerySEODAOController.java:78)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
at com.bloomscorp.loom.seo.dao.controller.TestProductImageGallerySEODAOController$$SpringCGLIB$$0.modifyGalleryImages(<generated>)
at com.bloomscorp.loom.seo.controller.ProductImageGallerySEOController.lambda$modifyGalleryImages$1(ProductImageGallerySEOController.java:126)
at com.bloomscorp.behemoth.controller.AbstractPostEntityController.post(AbstractPostEntityController.java:95)
at com.bloomscorp.behemoth.controller.AbstractPostEntityController.postEntityEnhancedResponse(AbstractPostEntityController.java:280)
at com.bloomscorp.behemoth.controller.AbstractPostEntityController.postEntity(AbstractPostEntityController.java:140)
at com.bloomscorp.behemoth.controller.AbstractPostEntityController.postEntity(AbstractPostEntityController.java:167)
at com.bloomscorp.loom.seo.controller.ProductImageGallerySEOController.modifyGalleryImages(ProductImageGallerySEOController.java:117)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at com.bloomscorp.nverse.validator.NVerseValidationAspect.validateByDomainValidator(NVerseValidationAspect.java:67)
at com.bloomscorp.nverse.validator.NVerseValidationAspect.validateDomainContext(NVerseValidationAspect.java:123)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:637)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:627)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:751)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
at com.bloomscorp.loom.seo.controller.ProductImageGallerySEOController$$SpringCGLIB$$0.modifyGalleryImages(<generated>)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:798)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
... 61 more
Also below is the controller code which invokes the modifyGalleryImages
function,
@ResponseBody
@PostMapping(
value = "modify/test/gallery-images",
consumes = Constant.REQUEST_TYPE_APPLICATION_JSON,
produces = Constant.RESPONSE_TYPE_APPLICATION_JSON
)
public RainTreeResponse modifyGalleryImages(
NVerseHttpRequestWrapper request,
@RequestBody TestProductImageGallerySEOPayload gallerySEOPayload
) {
return this.postEntity(
request,
"modifyGalleryImages",
CODE_SUCU,
UNAUTH_GALLERY_IMAGE_REQUEST,
NEW_GALLERY_IMAGE_CREATED,
this.testValidator,
new TestProductImageGallerySEOPayloadSanitizer(),
gallerySEOPayload,
() -> this.testSEODAOController.modifyGalleryImages(
this.getAuthorityResolver().resolveUserInformationFromAuthorizationToken(
request.getHeader(Constant.REQUEST_HEADER_AUTHORIZATION)
),
gallerySEOPayload
)
);
}
Please ignore the library level codes as those are already tested. The controller is invoking the modifyGalleryImages
method once a request hits the controller.
It seems that new version of Hibernate does not recognize ID field value of 0 to be a new record. Setting its value to null will solve it.