How to skip(not rollback) for exception in #Transactional method? - java

My goal is to validate update logic before it queries to the database. Also this validation should not rollback or fail on request. It should just skip failed iteration in for-loop logic(logging would be enough) and go next iteration.
I created custom Hibernate interceptor, that validates request "before SQL querying" at the end of transaction. That's how I understand it.
I check current and previous field value in boolean onFlushDirty overriten method and throw org.hibernate.CallbackException(as it throws in Interceptor interface method signature) if validation failes.
In my non-transactional service method I have for loop where I execute another method that can cause this exception. repository.save() method is transactional and method throws JpaSystemException instead of CallbackException when validation fails...
So I catch it and don't throw anything - just logging. That's why failed iteration is skipped and service method continues its work.
But I need to have transactional service method to have option to rollback for other exceptions and keep "skipping Hibernate interceptor exception when it caught" finishing method successfully.
I tried to add
#Transactional(noRollbackFor = JpaSystemException.class)
But it doesn't work for me. I also tried to noRollBackFor others exception, I tried to throw custom exceptions instead of CallbackException. It didn't fix problem.
What should I do in my situation?

Related

How to partially rollback data in spring boot, errors to be persited

I have sequence of table data creation in transaction, using springboot, java, jpa and hibernate sequence shown below. Database used is Oracle
Place order - insert into order details_table
Process Payment - insert into payment_info table
Error Message(info, warning, error) - insert into error_message table
I am inserting error messages in error_message table if any error occurs(info, warning, error).
My info and warning message get persisted into error_message if no error occurs during the processing of the request.
But if there is an error, I am throwing an error from code to rollback the transaction, its working but my error_message also get rollback.
I want to rollback details_table and payment_info but not the error_message, I want them to be persisted.
How I can achieve the same?
Use two different transactions.
The default transaction propagation is REQUIRED, when calling your #Transactional annotated method without a transactional context, a new transaction will be started but if there's already a transaction in place, the transaction will be joined, ending up with a single transaction. In case of rollback, all operations will be rollbacked as they belong to the same transaction.
Change the transaction propagation to REQUIRES_NEW in order to always force starting a new transaction (instead of joining it if there's one). This will allow you to commit the error messages transaction independently from the payment info.
#Transactional
public void placeOrder(Order oder) {
// process order
paymentGateway.processPayment();
// save order <- an exception here will rollback the current
// transaction but not the one in PaymentGateway which is independent
}
// In PaymentGateway
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPayment() {
// process payment
// insert error messages
}
Further reading: Understanding PROPAGATION_REQUIRED. Make sure you also understand how exceptions affect transactions.

Retry not working when Spring Batch managed transaction rolls back

In one of the steps of my Spring Batch job, I'm trying to configure it so that when ObjectOptimisticLockingFailureException happens, the step can be retried and hopefully the retry will work.
#Bean
public Step myStep(StaxEventItemReader<Response> staxEventResponseReader,
ItemWriter<Response> itemWriter,
ItemProcessor<? super Response, ? extends Response> responseProcessor) {
return stepBuilderFactory.get("myStep")
.<Response, Response>chunk(1)
.reader(staxEventResponseReader)
.processor(responseProcessor)
.writer(itemWriter)
//.faultTolerant().retryLimit(3).retry(Exception.class)
.build();
}
The logic of writer for the step is pretty simple: it tries to read a row from the database, and once it finds the row, it updates it. I was able to reproduce the ObjectOptimisticLockingFailureException by setting a breakpoint right after the find method, manually bump the version column for the row in database and commit it, then resume.
However, after uncommenting the retry definition in my step, no retries were attempted. After some debugging, it seems that the Spring retry logic is inside the chunk's transaction; but since the ObjectOptimisticLockingFailureException is not thrown by my code in the writer, but by Spring's chunk transaction committing logic, no retries were attempted at all.
Chunk Transaction Begin
Begin Retry loop in FaultTolerantChunkProcessor.write()
Writer logic in my Step
End Retry loop
Chunk Transaction Commit - Throws ObjectOptimisticLockingFailureException
When I tried to explicitly throw ObjectOptimisticLockingFailureException in my writer, the retry logic worked perfectly as expected. My questions are:
How to make the retry logic work if the exception is not thrown from my writer code in the step, but by the time the chunk transaction is committed by Spring Batch?
Another weird behavior is, when I manually cause the ObjectOptimisticLockingFailureException by bumping the version column in database, with the retry definition commented in the step, the final status of the step is FAILED which is expected. But with the retry definition uncommented, the final status of the step is COMPLETE. Why is that?
How to make the retry logic work if the exception is not thrown from my writer code in the step, but by the time the chunk transaction is committed by Spring Batch?
There is an open issue for that here: https://github.com/spring-projects/spring-batch/issues/1826. The workaround is to (try to anticipate and) throw any exception that might happen at the commit time in the writer. This is what you tried already and confirmed that works when you say When I tried to explicitly throw ObjectOptimisticLockingFailureException in my writer, the retry logic worked perfectly as expected.
Another weird behavior is, when I manually cause the ObjectOptimisticLockingFailureException by bumping the version column in database, with the retry definition commented in the step, the final status of the step is FAILED which is expected. But with the retry definition uncommented, the final status of the step is COMPLETE. Why is that?
This is related to the previous issue, but caused by a different one: https://github.com/spring-projects/spring-batch/issues/1189. That said, it is ok to play with the version field during a debugging session to understand how things work, but I would not recommend changing the version column in your code. Spring Batch relies on this column heavily in its optimistic locking strategy, and it is not expected to change values of this column in user code, otherwise unexpected behaviour might happen.

Could not roll back Hibernate transaction; nested exception is org.hibernate.TransactionException: JDBC rollback failed

I have an application which contains a webservice that use Spring and hibernate in order to retrieve data from database.
Other applications invoke this webservice in their application.
The flow of webservice :
Webservice class method (calls manager class method) --> Manager class method ---> that calls DAO method )
Webservice class :
public List getXXX(){
managerClass.getXX();
}
manager Class:
#Transactional(readOnly=true)
Public List getXX(){
DAOClass.getX();
}
DAO class(It is using hibernate and logic to retrieve data) :
public List getX(){
Criteria query
=getSessionFactory().getCurrentSession()
.createCriteria(dataObject.class);
query.add(Restrictions.eq(dataObject.type, systemId));
query.addOrder(Order.asc(dataObject.TYPE));
query.addOrder(Order.asc(dataObject.code));
return query.list();
}
This Webservice method(by calling manager and DAO classes) giving the list from database. It is working fine all the time except sometimes.
I am not updating or inserting any data,I am just getting the data
(The flow is : Webservice --> manager Class--> DAO --> hibernate)
One client is accessing this method to get the List in their application.he is getting the data properly all the time.But sometimes he is getting this Exception :
javax.xml.ws.soap.SOAPFaultException: Could not roll back Hibernate transaction; nested exception is org.hibernate.TransactionException: JDBC rollback failed
at org.apache.axis2.jaxws.marshaller.impl.alt.MethodMarshallerUtils.createSystemException(MethodMarshallerUtils.java:1346)
at org.apache.axis2.jaxws.marshaller.impl.alt.MethodMarshallerUtils.demarshalFaultResponse(MethodMarshallerUtils.java:1072)
at org.apache.axis2.jaxws.marshaller.impl.alt.DocLitWrappedMethodMarshaller.demarshalFaultResponse(DocLitWrappedMethodMarshaller.java:593)
at org.apache.axis2.jaxws.client.proxy.JAXWSProxyHandler.getFaultResponse(JAXWSProxyHandler.java:559)
at org.apache.axis2.jaxws.client.proxy.JAXWSProxyHandler.createResponse(JAXWSProxyHandler.java:497)
at org.apache.axis2.jaxws.client.proxy.JAXWSProxyHandler.invokeSEIMethod(JAXWSProxyHandler.java:404)
at org.apache.axis2.jaxws.client.proxy.JAXWSProxyHandler.invoke(JAXWSProxyHandler.java:208)
According to Spring default behavior, whenever the exception is thrown, it tries to roll back.That is what it is trying, but somehow could not rollback. As it is declarative transaction by using annotations, How can I track exact reason for this exception? based on the exception, I definitely know the DAO class method is throwing exception.but I am not able to track the exact reason(whether connection closed issue or any other thing).i just want to use exception handling in DAO class in order to know the exact reason.Can I use try/catch block in DAO class method to have stack trace that prints the exact reason?
I just want to remind once again, it is working fine all the time, but sometimes it is not working because of this exception.
I hope I clearly explained my problem.Please do the needful by giving solution.

Intercepting #Transactional After Optimistic Lock for Asynchronous Calls in Restful App

The question I have today is how to retry a method after the #Transactional annotation causes an Optimistic Lock Exception (OLE) and rolls back the transaction.
I have asynchronous calls to a Restful application that are attempting to update a database object based on some business logic. If I get an OLE, I'd like to retry the transaction after a delay of 0.2-0.5 seconds.
#Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED, readOnly = false)
public Response myMethod(Long myParam) throws Exception {
~Call to update db using hibernate after business logic~;
return Response.ok().build();
}
I've tried using AspectJ to intercept my method after it throws the OLE so that I can retry. However, the issue is the #Transactional annotation. My method is not throwing the error message since business logic is not failing. Instead, myMethod returns a 200 response, but the OLE exception is encountered and then thrown in the ResourceJavaMethodDispatcher.java class that is responsible for invoking myMethod.
My aspect class:
#Aspect
public class myAspect {
#AfterThrowing(value = "execution(* com.package.blah.myClass.myMethod(..)) && args(.., myParam)", throwing = "ex")
public Response catchAndRetry(JoinPoint jp, Throwable ex, Long myParam) throws Throwable {
Response response = null;
response = invokeAndRetry(jp, myParam);
return response;
}
}
The invokeAndRetry() method has the logic to call wait on the thread and then retry up to a maximum of three tries.
I can successfully get into myAspect from an exception thrown by business logic; but the OLE thrown from the transaction does not get caught in myAspect.
Having said all of that, is there a way to wrap/encapsulate/intercept the #Transaction annotation in order to run my retry logic?
Side notes:
1) I've looked into creating my own #Retry annotation based on the example here. I've used that dependency to try his #Retry annotation, but to no avail.
2) I'll be looking into Spring's #within to see if that could prove useful.
The short answer is: you shouldn't try to reuse an EntityManager after an exception occurs. According to the Hibernate EntityManager User guide on Transactions and concurrency, which most probably applies to all JPA providers:
If the EntityManager throws an exception (including any SQLException), you should immediately rollback the database transaction, call EntityManager.close() (if createEntityManager() has been called) and discard the EntityManager instance. Certain methods of EntityManager will not leave the persistence context in a consistent state. No exception thrown by an entity manager can be treated as recoverable. Ensure that the EntityManager will be closed by calling close() in a finally block. Note that a container managed entity manager will do that for you. You just have to let the RuntimeException propagate up to the container.
You might be able to do the retry the operation in a new transaction with a new instance of an EntityManager though, but that's a different use-case.
After doing some research and looking at some more tutorials, I found a way to have my aspect take precedence over #Transactional. Just below the #Aspect tag, I added the annotation #Order(1).
This gives my aspect higher priority since #Transactional is defaulted to Ordered.LOWEST_PRECEDENCE. See Spring documentation for some more details about #Order.

Force Hibernate To Save A Specific POJO

I have some code calling a webservice and it returns an ID. I am saving this ID in the database using hibernate. I have a filter that opens the session and commits it, rolling back when any exception occurs within the contained code. Since there is no way to get back the ID returned by the webservice I would like to save this in the database EVEN if there is an exception that occurred later on in the code. Is there anyway to do this using the same session?
It depends on who throws the exception. If it is thrown by the hibernate session object you cannot reuse the session anymore as stated in hibernate Session documentation:
If the Session throws an exception, the transaction must be rolled back and the session discarded. The internal state of the Session might not be consistent with the database after the exception occurs.
If the exception is thrown by some other code, then yes, you can do something like this:
Long yourIdFromWebservice = ...
try {
// do some processing that might result in an exception
}
catch(//the interesting exception, but not HibernateException) {
//maybe log it, rethrow it
}
finally {
session.save() //save what needed to be saved
}
or depending on your use case:
try {
// do some processing that might result in an exception
session.save() //save all stuff if no exception
}
catch(//the interesting exception, but not HibernateException) {
session.save() //save only the id from the webservice
}
The right way to do that is to use two transactions: one to save the ID, and one to save the rest of your work.
If you're using EJB, you could do the save of the ID in a bean of its own, and annotate it with a TransactionAttribute of REQUIRES_NEW.
Otherwise, you can look at what control over transactions your environment gives you, and figure out a way to have two of them.

Categories