Почему я получаю «ObjectOptimisticLockingFailureException» с «INSERT ... ON DUPLICATE KEY UPDATE»? - PullRequest
0 голосов
/ 11 марта 2020

У нас есть микросервис, который читает и пишет в базу данных MySQL. Он настроен на одновременное получение до 5 сообщений от конечной точки Camel .from(jms:queue:queuaName). Полученные сообщения затем сохраняются в базе данных MySQL.

Для операций с базой данных используется JpaRepository, поэтому есть доступ к findById(..), save(..), а также для этого несколько пользовательских методов.

Существует несколько маршрутов Camel, и первый имеет .transacted(), поэтому все они являются частью одной транзакции. Также мы устанавливаем @Transactional(propagation = Propagation.MANDATORY, isolation = Isolation.READ_COMMITTED) для метода, который сохраняет сообщения в базу данных:

@Transactional(propagation = Propagation.MANDATORY, isolation = Isolation.READ_COMMITTED)
public boolean methodForSaving(Exchange exchange) {
..
Entity<S> entity = messageRepository.findById((long) key.hashCode());

if (entity != null) {
    entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE);
    entityManager.refresh(entity);
}

entity = setEntity(object, key, messageIdentifier, entity);
..
messageRepository.save(entity);
..
}

Вот проблема - любые два сообщения могут иметь одинаковый идентификатор ( PRIMARY KEY) (генерация идентификатора выполняется вручную), поэтому бывают случаи, когда 2 разных сообщения с одинаковым идентификатором вставляются в MySQL, а затем мы видим o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '-1411515457' for key 'PRIMARY' исключение.

Чтобы решить эту проблему, мы попытались добавить:

@SQLInsert(sql="INSERT INTO tableName(id, vara1, vara2, varb1, varb2, varc1, varc2, vard1, vard2, vare1, vare2, varf1, varf2, varg1, varg2, varh1, varh2, vari1, vari2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE vara1 = False, vara2 = False, varb1 = 'test', varb2 = 'test2', varc1 = 'bread', varc2 = 'crumb', vard1 = 'eventr', vard2 = 'eference', vare1 = IF(ISNULL(VALUES(vare1)), vare1, VALUES(vare1)), vare2 = IF(ISNULL(VALUES(vare2)), vare2, VALUES(vare2)), varf1 = IF(ISNULL(VALUES(varf1)), varf1, VALUES(varf1)), varf2 = IF(ISNULL(VALUES(varf2)), varf2, VALUES(varf2)), varg1 = IF(ISNULL(VALUES(varg1)), varg1, VALUES(varg1)), varg2 = IF(ISNULL(VALUES(varg2)), varg2, VALUES(varg2)), varh1 = 'varh1', varh2 = 'varh2', vari1 = 'uref1', vari2 = 'uref2'")` to the class that has annotations and represents a table - `@Entity
@Table(name = "tableName")`

, но выдается следующее исключение:

org.springframework.orm.ObjectOptimisticLockingFailureException: 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.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:320) ~[spring-orm-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521) ~[spring-orm-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) ~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:150) ~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.apache.camel.spring.spi.TransactionErrorHandler.doInTransactionTemplate(TransactionErrorHandler.java:176) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.processInTransaction(TransactionErrorHandler.java:136) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.process(TransactionErrorHandler.java:105) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.process(TransactionErrorHandler.java:114) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:76) ~[camel-core-2.20.2.jar:2.20.2]
    at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:548) ~[camel-core-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.processByErrorHandler(TransactionErrorHandler.java:220) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler$1.doInTransactionWithoutResult(TransactionErrorHandler.java:183) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.springframework.transaction.support.TransactionCallbackWithoutResult.doInTransaction(TransactionCallbackWithoutResult.java:34) ~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133) ~[spring-tx-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.apache.camel.spring.spi.TransactionErrorHandler.doInTransactionTemplate(TransactionErrorHandler.java:176) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.processInTransaction(TransactionErrorHandler.java:136) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.process(TransactionErrorHandler.java:105) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.spring.spi.TransactionErrorHandler.process(TransactionErrorHandler.java:114) ~[camel-spring-2.20.2.jar:2.20.2]
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:201) ~[camel-core-2.20.2.jar:2.20.2]
    at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:201) ~[camel-core-2.20.2.jar:2.20.2]
    at org.apache.camel.processor.DelegateAsyncProcessor.process(DelegateAsyncProcessor.java:97) ~[camel-core-2.20.2.jar:2.20.2]
    at org.apache.camel.component.jms.EndpointMessageListener.onMessage(EndpointMessageListener.java:112) ~[camel-jms-2.20.2.jar:2.20.2]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:719) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:679) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:649) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:317) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:235) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1166) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1158) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1055) ~[spring-jms-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_211]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_211]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_211]
    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:67) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:54) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:46) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3134) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3013) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3393) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:145) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ~[spring-orm-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    ... 32 common frames omitted

Даже если выдается исключение o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '-1411515457' for key 'PRIMARY', мы можем определить .onException() route, поймайте ConstraintViolationException и выполните повторную доставку, это решает проблему, но, похоже, нет способа как отключить исключение из журналов, поскольку оно выбрасывается где-то за пределами маршрутов Camel. Любое предложение о том, как отключить исключение, будет приветствоваться.

...