hachiko
hachiko

Reputation: 131

ObjectOptimisticLockingFailureException and RollbackException

I have a problem with hibernate and jpa.

I have a method that is called regularly to retrieve data from a table in database and to send it to another server.

Here is the code of the method :

protected List<MessagingMessage> getNewMessages() throws NoMessageAvailableException {
    return messagingPublisher.getNewMessages(application);
}

Here is the code from the publisher :

@Override
@Transactional(noRollbackFor = {ObjectOptimisticLockingFailureException.class})
public List<MessagingMessage> getNewMessages(String application) throws NoMessageAvailableException {
    List<GpsDataToSend> allGpsDatas = gpsDataToSendService.findByapplicationIgnoreCase(application);
    List<MessagingMessage> allMessages = new ArrayList<>();
    for (GpsDataToSend gpsDataToSend : allGpsDatas) {
        MessagingMessage message = convertToMessagingMessage(gpsDataToSend);
        // Ajout dans les messages à envoyer au TMS
        allMessages.add(message);
        try {
            // Suppression des données de la base
            gpsDataToSendService.deleteGpsDataToSend(gpsDataToSend.getId());
        } catch (ObjectOptimisticLockingFailureException oolfa) {
            // Si l'exception est levée, c'est que la donnée a été supprimée, donc déjà été envoyée au TMS
            allMessages.remove(message);
            Long idGpsDataToSend = ((GpsDataToSend) message.getObject()).getId();
            log.info("The GPS message was already sent because the data doesn't exists anymore in database (id : {})", idGpsDataToSend);
        }
    }
    return allMessages;
}

The publisher retrieves the data, convert it in a message, and the data is deleted from the database, to be sure that it is only send once.

The problem is that I have 2 servers in loadbalancing, executing the same code, so potentially the 2 servers are retrieving the same data at the same time. Sometimes, on the delete, I had an ObjectOptimisticLockingFailureException that occured, telling me that there was no data to delete. I just handled this Exception to make sure it didn't mess with the rest of the execution.

But now, when an ObjectOptimisticLockingFailureException occurs, at the next execution of :

protected List<MessagingMessage> getNewMessages() throws NoMessageAvailableException {
    return messagingPublisher.getNewMessages(application);
}

I get this exception :

2018-04-24 15:58:09 ERROR [pool-3-thread-1] c.g.m.m.w.WepSphereMqQueuePusher [WepSphereMqQueuePusher.java:76] Error getting new message to send org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526) ~[spring-orm-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) ~[spring-tx-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518) ~[spring-tx-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292) ~[spring-tx-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at com.sun.proxy.$Proxy75.getNewMessages(Unknown Source) ~[na:na]
at com.geodisbm.mobility.messaging.websphere.WepSphereMqQueuePusher.getNewMessages(WepSphereMqQueuePusher.java:101) [messaging-1.0-SNAPSHOT.jar:na]
at com.geodisbm.mobility.messaging.websphere.WepSphereMqQueuePusher.sendAllMessages(WepSphereMqQueuePusher.java:71) [messaging-1.0-SNAPSHOT.jar:na]
at com.geodisbm.mobility.messaging.websphere.WepSphereMqQueuePusher.access$000(WepSphereMqQueuePusher.java:16) [messaging-1.0-SNAPSHOT.jar:na]
at com.geodisbm.mobility.messaging.websphere.WepSphereMqQueuePusher$1.run(WepSphereMqQueuePusher.java:63) [messaging-1.0-SNAPSHOT.jar:na]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_45]
at java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:308) [na:1.8.0_45]
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java) [na:1.8.0_45]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_45]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_45]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_45]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_45]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_45]
Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:58) ~[hibernate-entitymanager-5.1.12.Final.jar:5.1.12.Final]
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ~[spring-orm-4.3.14.RELEASE.jar:4.3.14.RELEASE]
... 20 common frames omitted

It appears that if the ObjectOptimisticLockingFailureException occurs, the transaction is marked as rollbackonly. So I tried to say the transaction to not rollback for this exception (via the annotation), but it doesn't work.

Do you have any idea why it's not working ?

Thanks.

Upvotes: 2

Views: 2756

Answers (1)

Most probably this problem happens because getNewMessages invokes some method that is transactional and transaction manager's property globalRollbackOnParticipationFailure is set to true (which is the default).

Here's the relevant piece of javadoc:

 Set whether to globally mark an existing transaction as rollback-only
 after a participating transaction failed.
 <p>Default is "true": If a participating transaction (e.g. with
 PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing
 transaction) fails, the transaction will be globally marked as rollback-only.
 The only possible outcome of such a transaction is a rollback: The
 transaction originator <i>cannot</i> make the transaction commit anymore.
 <p>Switch this to "false" to let the transaction originator make the rollback
 decision. If a participating transaction fails with an exception, the caller
 can still decide to continue with a different path within the transaction.
 However, note that this will only work as long as all participating resources
 are capable of continuing towards a transaction commit even after a data access
 failure: This is generally not the case for a Hibernate Session, for example;
 neither is it for a sequence of JDBC insert/update/delete operations.

You have set noRollbackFor on the outer transaction but

  1. getNewMessages never throws ObjectOptimisticLockingFailureException
  2. more importantly the transaction is marked as rollbackOnly when some other method (I guess deleteGpsDataToSend is annotated with @Transactional) throws that exception.

Upvotes: 3

Related Questions