-1

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.

FrancescoDS
  • 1,077
  • 4
  • 21
  • 55
  • If this is an existing database with "legacy" tables you are trying to use, please show the SQL for creating the tables. I would guess that your mappings are wrong. It would also be nice to get the code before save - specifically, did you create the object t with new, or is it an existing/detached object? – ewramner Jun 09 '22 at 16:22
  • You have a @Version annotation or mapping in your entity (or some Hibernate specific equivalent). This forces JPA providers to check the version value and throw an optimistic lock exception if it doesn't line up with the data in the database - preventing stale overwrites. – Chris Jun 09 '22 at 18:09
  • @ewramner, I've added the scripts of the tables involved. I've create the object as usual in Spring MVC way (with a jsp form that fills a bean with form values inside) – FrancescoDS Jun 10 '22 at 07:08
  • @Chris there is no Version annotation or similar in the entities, that's why I don't understand the exception... – FrancescoDS Jun 10 '22 at 07:08
  • If there is no version available Hibernate can compare all fields instead to see if they have been changed. However, it is odd to get that from a call to save. Are you sure the exception is triggered here and not later on in the code? And what is the SQL it tries to execute to do the save? – ewramner Jun 10 '22 at 10:47
  • @ewramner I've added in the question the stacktrace with the queries executed. The error comes up when I call the save() for sure, not in another part of the code. – FrancescoDS Jun 10 '22 at 11:00
  • I still wonder why you are getting both an update and an insert. Can you add logging of the parameters (https://stackoverflow.com/questions/2536829/hibernate-show-real-sql) so we can see what it is trying to do? I'm suspecting that there are other instances in the session so that you are seeing two different instances here. – ewramner Jun 10 '22 at 13:49
  • there is only one instance that calls the insert first and then the update of the same object type. The problem is that the TipiComposizioni object inside TariffeBaseComposizioni is enhanced only with "sigla" value, the "id" is null. Setting the correct value of "id" in TipiComposizioni object solves the problem and saves the TariffeBaseComposizioni object correctly...but why? – FrancescoDS Jun 10 '22 at 15:00

3 Answers3

1

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 (e.g. @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.

XtremeBaumer
  • 6,275
  • 3
  • 19
  • 65
  • solution 3 is the best in my case, and I suppose it is in all cases! thanks to @XtremeBaumer for precious help! – FrancescoDS Jun 21 '22 at 10:12
0

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();
CaptainPyscho
  • 338
  • 1
  • 7
  • I've tried your solution, adding a flush() after the save(), but nothing changes. The missing code is the JSP part, in which I set values of the TariffeBaseComposizioniBean object. The form has a select that allows user to set the TipiComposizioniBean object, in which the itemValue is "sigla", besides the other fields regarding "prezzo" and "punti", that are two Double values – FrancescoDS Jun 20 '22 at 20:50
0

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?)

Davide D'Alto
  • 7,421
  • 2
  • 16
  • 30
  • I do not call any update in my flow, I simply try to save a TariffeBaseComposizioniBean object, that has a reference to a TipiComposizioniBean object with a null id and a value in sigla field. I've also tried with NaturalId annotation on sigla, but the exception does not change – FrancescoDS Jun 20 '22 at 20:34