简体   繁体   中英

HibernateOptimisticLockingFailureException when saving an entity in Spring MVC with JPA

In my Java project (Spring MVC) I have the following entities:

public class TariffeBaseComposizioni implements Serializable {

 private static final long serialVersionUID = 1L;

 @Id
 @Basic(optional = false)
 @NotNull
 @Column(name = "ID")
 private Long id;
 ...
 @JoinColumn(name = "TIPO_COMPOSIZIONE", referencedColumnName = "SIGLA")
 @ManyToOne
 private TipiComposizioni tipoComposizione;
 ...
}

public class TipiComposizioni implements Serializable {

private static final long serialVersionUID = 1L;

 @Id
 @Basic(optional = false)
 @NotNull
 @Column(name = "ID")
 private Long id;

 @Size(max = 20)
 @Column(name = "SIGLA")     
 private String sigla;

 @Size(max = 255)
 @Column(name = "NOME")
 private String nome;
 ...
}

I'm trying to save my TariffeBaseComposizioni instance with the save():

getCurrentSession().save(t);

where 't' is of type 'TariffeBaseComposizioni'.

When save is called, the HibernateOptimisticLockingFailureException is raised.

Here the stacktrace of the error, where the queries executed are visible:

Hibernate: insert into TARIFFE_BASE_COMPOSIZIONI (ID_FASCIA, PREZZO, PUNTI, TIPO_COMPOSIZIONE, ID) values (?, ?, ?, ?, ?)
Hibernate: update TARIFFE_BASE_COMPOSIZIONI set ID_FASCIA=?, PREZZO=?, PUNTI=?, TIPO_COMPOSIZIONE=? where ID=?
2022-06-10 12:55:02 INFO  AbstractBatchImpl:195 - HHH000010: On release of batch it still contained JDBC statements
2022-06-10 12:55:02 ERROR GlobalExceptionHandler:15 - Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at   org.springframework.orm.hibernate4.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:181)
at org.springframework.orm.hibernate4.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:680)
at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:562)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:755)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy163.save(Unknown Source)
at it.openingcode.ep.web.controller.TariffeBaseComposizioniController.doUpdate(TariffeBaseComposizioniController.java:210)
at it.openingcode.ep.web.controller.TariffeBaseComposizioniController.doUpdate(TariffeBaseComposizioniController.java:36)
at it.openingcode.ep.web.AbstractCrudController.update(AbstractCrudController.java:163)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:494)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:1025)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1137)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2575)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2564)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:81)
at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:73)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:59)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3224)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3126)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3456)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:140)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:364)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:356)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:278)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:328)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1234)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:404)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:554)
... 80 more

I've understood that the problem is that in my object there is a TipiComposizioni entity with a null id (it has only SIGLA value inside). In Oracle table, TIPI_COMPOSIZIONI has a natural key with the field SIGLA.

I've solved the issue loading the whole TipiComposizioni entity from database, with the ID value, but I would like to understand why Hibernate raises that exception and if there is another way to solve it. I thought about some editing in JPA annotations in model class, but nothing works.

EDIT:

here the scripts of the two involved Oracle tables:

CREATE TABLE TARIFFE_BASE_COMPOSIZIONI
(
  ID                 NUMBER(11),
  ID_FASCIA          NUMBER(11),
  TIPO_COMPOSIZIONE  VARCHAR2(20 BYTE),
  PREZZO             NUMBER(15,5)               DEFAULT 0,
  PUNTI              NUMBER(11)                 DEFAULT 0
);


CREATE UNIQUE INDEX NK_TARIFFE_BASE_COMPOSIZIONI ON TARIFFE_BASE_COMPOSIZIONI
(ID_FASCIA, TIPO_COMPOSIZIONE);

CREATE UNIQUE INDEX PK_TARIFFE_BASE_COMPOSIZIONI ON TARIFFE_BASE_COMPOSIZIONI
(ID);



ALTER TABLE TARIFFE_BASE_COMPOSIZIONI ADD (
 CONSTRAINT PK_TARIFFE_BASE_COMPOSIZIONI
 PRIMARY KEY
 (ID)
 USING INDEX PK_TARIFFE_BASE_COMPOSIZIONI
 ENABLE VALIDATE);

ALTER TABLE TARIFFE_BASE_COMPOSIZIONI ADD (
 CONSTRAINT FK_TABC_FASC 
 FOREIGN KEY (ID_FASCIA) 
 REFERENCES FASCE (ID)
 ENABLE VALIDATE,
 CONSTRAINT FK_TABC_TICO 
 FOREIGN KEY (TIPO_COMPOSIZIONE) 
 REFERENCES TIPI_COMPOSIZIONI (SIGLA)
 ENABLE VALIDATE);

---------------

CREATE TABLE TIPI_COMPOSIZIONI
(
  ID     NUMBER(11),
  SIGLA  VARCHAR2(20 BYTE)                      NOT NULL,
  NOME   VARCHAR2(255 BYTE)                     NOT NULL
);

CREATE UNIQUE INDEX NK_TIPI_COMPOSIZIONI ON TIPI_COMPOSIZIONI
 (SIGLA);

CREATE UNIQUE INDEX PK_TIPI_COMPOSIZIONI ON TIPI_COMPOSIZIONI
 (ID);

ALTER TABLE TIPI_COMPOSIZIONI ADD (
  CONSTRAINT PK_TIPI_COMPOSIZIONI
  PRIMARY KEY
  (ID)
  USING INDEX PK_TIPI_COMPOSIZIONI
  ENABLE VALIDATE,
  CONSTRAINT NK_TIPI_COMPOSIZIONI
  UNIQUE (SIGLA)
  USING INDEX NK_TIPI_COMPOSIZIONI
  ENABLE VALIDATE);

EDIT 2: I've added the following in Class definitions (I forgot to add it when I wrote the question):

private static final long serialVersionUID = 1L;

does the serialVersionUID affects the save() process?

I've also noticed that Hibernate performs an INSERT INTO query and after that tries to perform an UPDATE query on the same TARIFFE_BASE_COMPOSIZIONI table.

As you stated in your question already, the issue is that TipiComposizioni has no value on the id field. What is not quite clear is the reason you have the separate ID field, if you already have the natural key on sigla .

Since you don't specify that the @Id of TipiComposizioni is generated by the database in any way (eg @GeneratedValue(strategy = GenerationType.IDENTITY) ), you have to supply the id yourself. So basically, you have 3 ways to fix this:

1: Auto-generate the id value for TipiComposizioni and add @GeneratedValue(strategy = GenerationType.IDENTITY) to the id field. This requires you to change the database:

public class TipiComposizioni implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // the actual GenerationType can differ
    @Basic(optional = false)
    @NotNull
    @Column(name = "ID")
    private Long id;

    @Size(max = 20)
    @Column(name = "SIGLA")
    private String sigla;

    @Size(max = 255)
    @Column(name = "NOME")
    private String nome;

}

2: Manually supply the id value. You can keep the mapping and database as it is, but you have to manage the id yourself.

3: As you stated, private String sigla; is already a natural key, so you can simply remove the @Id from private Long id; and add it to private String sigla; . As you already provide a value for sigla , you don't have to worry about more.

public class TipiComposizioni implements Serializable {
    
    @Basic(optional = false)
    @NotNull
    @Column(name = "ID")
    private Long id;

    @Id
    @Size(max = 20)
    @Column(name = "SIGLA")
    private String sigla;

    @Size(max = 255)
    @Column(name = "NOME")
    private String nome;

}

Personally I would go with solution 3, as this causes the least work in my opinion.

I think there is more code involved causing the problem. This solution that I am writing is just a workaround, but the real problem is a mistery because we don´t have the entire code. Just call getCurrentSession().flush() after calling getCurrentSession().save(t)

getCurrentSession().save(t);
getCurrentSession().flush();

Maybe mapping the attribute sigla as natural identifier will help:

public class TipiComposizioni implements Serializable {

private static final long serialVersionUID = 1L;

 @Id
 // Is this correct? You said the id is null for a row on the db
 @Basic(optional = false)
 @NotNull
 @Column(name = "ID")
 private Long id;

 @NaturalId
 @Size(max = 20)
 @Column(name = "SIGLA")     
 private String sigla;

 @Size(max = 255)
 @Column(name = "NOME")
 private String nome;
 ...
}

When you update TariffeBaseComposizioni:

String sigla = ...
TariffeBaseComposizioni tariffe =  ...


TipiComposizioni tipo = getCurrentSession()
    .bySimpleNaturalId( TipiComposizioni.class )
    .getReference(sigla ); // Assuming you know already it exists in the db
tariffe.setTipoComposizione( tipi );
...

I think the fact that some of the identifiers in TipiComposizioni are null is causing issues when Hibernate create the association, making it believes that the entity has changed while it was updating the association (I haven't tested this theory).

Using getReference shouldn't cause the execution of any additional query to select or update TipiComposizioni . Unless something changes in tipo (Which column the update query in your question was trying to modify?)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM