Catching DB exception

Hi,
In a service task, I have to save a data on MySql DB and this save operation can throw a unique key constraint exception. When this exception happens, it would be a duplicate data and need to terminate the process such as sending it to a user task. So, how can I catch the exception the a delegate class? It may be related to the transaction boundary…

Hi,

Lets look at two cases where you dont catch the exception. If your implementation does not use asynchronous continuations, then the calling client will ultimately be exposed to the exception. If the exception occurs in a job executor thread, then after n retries an incident will be created.

One way to avoid the above is to catch the exception in your delegate code and then in your exception handler, throw a new BpmnError() and in your process model catch the BpmnError and route the process appropriately…

This example uses a similar technique, however note this example is really showing how to manage this kind of situation inside a job executor thread, however the detail is relevant…

regards

Rob

Thanks Rob,
Let me clarify my problem: I am not able to catch the exception in my delegate function. I believe the transaction is managed by Camunda and Spring with Hibernate. So, my ‘save’ function in my code doesn’t throw an error until the commit happens.

This is some error trace. I added @Transactional annotation with a Propagation.REQUIRES_NEW, but I don’t see my code in the long stack trace. I am pretty new to Camunda and I may miss the Transaction mechanism in Camunda (I read Camunda doc)

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:248)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:214)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:150)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:43)
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:69)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:32)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:50)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:43)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.executeJob(ExecuteJobsRunnable.java:91)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.run(ExecuteJobsRunnable.java:60)

Hi,
From the stack trace I can see this is occuring in an async continuatiion and thus a job executor thread. I suspect the issue is here;

I assume you have some business object your persisting to the same database as the engine. If thats the case, do you really need a new transaction? If I co-locate business objects in the engine’s DB, I rely on the engines transaction to keep business object and process state in sync.

Its been a long time since I looked at Spring transactions, so Im not sure what a new transaction inside an existing transaction results in, but in dev, if you remove this annotation, does the problem go away?

regards

Rob


I try to achieve the process shown above. In fact, the process shown above is not required. This is sudo-code I try to do.

  • current code

@Override
public void execute(DelegateExecution execution) throws Exception {
save Tdo
}

  • To-Do: During this save, we now can have an unique key constraint. In this case, I detect/catch an exception and set a variable ‘isResponsePersisted’ to false and send it to a user task.

So, I can not remove the @Transactional annotation because the commit happens out side of our code.

  • My Tries

@Override
public void execute(DelegateExecution execution) throws Exception {
try{
persistTdo(Tdo)
set the variable to true
}catch(Exception ex){
set the variable to false
}
}

Method 1:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void persistTdo(Tdo){
save Tdo
}
→ I expect the transaction catch an exception, roll back, and pass an exception to the caller.

Method 2:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void persistTdo(Tdo){
try{
saveAndFlush Tdo
}catch (RuntimeException ex){
throw ex
}
}

I even tried this:
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = RuntimeException.class)

I am getting this error in the cockpit instance, and Camunda puts the process in a failed status. I am using Camunda 7.10

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)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:150)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:43)
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:69)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:32)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:50)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:43)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.executeJob(ExecuteJobsRunnable.java:91)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.run(ExecuteJobsRunnable.java:60)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:74)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
… 13 more

Thanks for your time.

Hi,
looks like the inner transaction on failure forces the outer transaction to rollback. The outer transaction is the job executor…

Its still not clear to me if your inner transaction is same or separate DB though?

This reference to named transaction managers may be of use, particularly if you need to manage your business data transaction separately

regards

Rob

And, yes Rob,
Before I change any code (adding any annotation), I also tried. Same error during the commit, but different message.

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:248)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:214)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:150)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:43)
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:69)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:32)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:50)
at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:43)

Oh, we posted almost same time. :wink:
Ok. I will look at the reference you posted. Thank you.

BTW, I believe the error is generated from inner method: persistTdo. I added a few logs and see a log in the persistTdo, but not in the execute method. I believe your outer means the transaction started before the execute method.

Hi
This spring tx thread may be of interest…

regards

Rob

Rob.
Thanks a lot. The spring tx thread page gave me a solution.

" Apparently you are calling a method within the same class, so Spring can’t intercept the call and apply a transactional proxy (the REQUIRES_NEW propagation is ignored). You should migrate the update method to another Spring bean"

1 Like

How to catch such DB exception in bpmn without using delegate code.

For example:
I am trying to create camunda user, but if its duplicate → as expected it should give unique constrint violation exception.
Hence how handle it without delegate code withing the service task. Below does not catch the error:

try{
execution.getProcessEngine().getIdentityService().saveUser(person);
}
catch(e){
throw new org.camunda.bpm.engine.delegate.BpmnError(“jdbcUpdateException”);
}