#Transactional on #Async methods in Spring - java

I have a scenario where I call three #Transactional #Async methods. Everything works fine except all three methods have their own transaction context. I want to execute them in calling method's transaction context.
My Calling method is like this:
#Transactional
public void execute(BillingRequestDto requestDto) {
try {
LOGGER.info("Start Processing Request : {}", requestDto.getId());
List<Future<?>> futures = new ArrayList<>();
futures.add(inboundProcessingService.execute(requestDto));
futures.add(orderProcessingService.execute(requestDto));
futures.add(waybillProcessingService.execute(requestDto));
futures.stream().parallel().forEach(future -> {
try {
future.get();
} catch (Exception e) {
futures.forEach(future1 -> future1.cancel(true));
throw new FBMException(e);
}
});
requestDto.setStatus(RequestStatus.SUCCESS.name());
requestDto.setCompletedAt(new Date());
LOGGER.info("Done Processing Request : {}", requestDto.getId());
} catch (Exception e) {
requestDto.setStatus(RequestStatus.FAIL.name());
requestDto.setCompletedAt(new Date());
throw new FBMException(e);
}
}
And all called methods are annotated with #Async and #Transactional.
#Transactional
#Async
public Future<Void> execute(BillingRequestDto requestDto) {
LOGGER.info("Start Waybill Processing {}", requestDto.getId());
long count = waybillRepository.deleteByClientNameAndMonth(requestDto.getClientName(), requestDto.getMonth());
LOGGER.info("Deleted {} Records for Request {} ", count, requestDto.getId());
try (InputStream inputStream = loadCsvAsInputStream(requestDto)) {
startBilling(requestDto, inputStream);
} catch (IOException e) {
LOGGER.error("Error while processing");
throw new FBMException(e);
}
LOGGER.info("Done Waybill Processing {}", requestDto.getId());
return null;
}
Implementation for all three methods is more or less same.
Now if there is a failure in any of these methods then transaction in rolled-back for that method only.
My requirement is to run all three methods in calling methods transaction context so any exception in one method will rollback all three methods.
This scenario works well if I disable #Async. There are time taking methods so I want them to run in parallel.
Please suggest any solution for this.

I guess you should use spring TransactionTemplate for programmatic control.
The main thread should perform control, if any thread throws exception you should notify the others thread that they should be rollbacked.
Say, each "transactional" thread after execution should wait(), in case of no exception just perform notifyAll() and in your thread do transaction commit, in case of exception you should call Thread.interrupt() and do rollback.

I think that yours #Async method can throw checked exception
#Transactional
#Async
public Future<Void> execute(BillingRequestDto requestDto) throw RollBackParentTransactionException {
...
}
and the caller can be annotated with:
#Transactional(rollbackFor = RollBackParentTransactionException.class)
public void execute(BillingRequestDto requestDto) { ... }
that should work.

Related

how to rollback on caught exception in Play framework?

I recently got into problem with play framework 2 #Transactional. Based on my tests, in the case of an exception, a transactional method would only rollback if the exception is unchecked (no catch block).
Here's my controller:
#Transactional
public Result myController(){
ObjectNode result = Json.newObject();
try{
JsonNode json = request().body().asJson();
someFunction(json);
//doing some stuff using the json object inside someFunction
//which I may intentionally throw an exception
//based on some logic from within
//(using "throw new RuntimeException()")
result.put("success", true);
return ok(Json.toJson(result));
}catch(Exception e){
result.put("success", false);
result.put("msg", e.getMessage());
return internalServerError(Json.toJson(result));
}
}
I want my controller to always return a JSON in response. But this comes at the expense of not having a database rollback when I throw an exception in my code. I know that in spring you can add this to #Transactional annotation but I'm using play.db.jpa.Transactional. Is there any way I can do a rollback in my catch block without using spring?
The #Transactional annotation basically wraps your action's code in a call to DefaultJpaApi.withTransaction. If you look at the source you can see how this method handles the transaction.
Since you want to catch the exception, but still want to use the withTransaction behaviour, you could try removing the #Transactional annotation and calling withTransaction yourself within the action.
E.g.
class MyController {
private final JPAApi jpa;
#Inject
public MyController(JPAApi jpa) {
this.jpa = jpa;
}
public myAction() {
ObjectNode result = Json.newObject();
try {
JsonNode json = request().body().asJson();
// Calls someFunction inside a transaction.
// If there's an exception, rolls back transaction
// and rethrows.
jpa.withTransaction(() -> someFunction(json));
// Transaction has been committed.
result.put("success", true);
return ok(Json.toJson(result));
} catch(Exception e) {
// Transaction has been rolled back.
result.put("success", false);
result.put("msg", e.getMessage());
return internalServerError(Json.toJson(result));
}
}
}

Dropwizard #UnitOfWork with asynchronous database call

I imagine that this is a common problem, but after some searching I wasn't able to find anything relevant.
The problem I'm having is that I'm getting a No Hibernate Session bound to thread exception when annotating my resource method with #UnitOfWork and inside my resource method, making an asynchronous DAO call. The idea behind this design is to make the database call on a separate I/O thread so that it frees up the Jersey resource thread.
Unfortunately, as the exception says, this RxIoScheduler-2 thread doesn't have a hibernate session bound to it.
Any suggestions?
Hibernate Session is not thread safe, so we need a strategy how to get the current session for the current thread. Such strategy is called CurrentSessionContext.
The current session is a session which we get by this call:
sessionFactory.getCurrentSession()
Hibernate can be configured with various current session strategies. #UnitOfWork uses this strategy:
hibernate.current_session_context_class = managed
For this strategy you should put a session to the context by an explicit call of the
ManagedSessionContext.bind(session)
So, as we know a Session is not thread safe, you should create a new session for a separate thread and put that session in the ManagedSessionContext. After that you can call your DAO by the same way as in the endpoint methods with #UnitOfWork.
Keep in mind that you should unbind the session before closing it with
ManagedSessionContext.unbind(factory)
You can use this utility class to create a session for a separate thread:
public final class HibernateSessionUtils {
private HibernateSessionUtils() {
}
public static void request(SessionFactory factory, Runnable request) {
request(factory, () -> {
request.run();
return null;
});
}
public static <T> T request(SessionFactory factory, Supplier<T> request) {
Transaction txn = null;
Session session = factory.openSession();
try {
ManagedSessionContext.bind(session);
txn = session.beginTransaction();
T result = request.get();
commit(txn);
return result;
} catch (Throwable th) {
rollback(txn);
throw Throwables.propagate(th);
} finally {
session.close();
ManagedSessionContext.unbind(factory);
}
}
private static void rollback(Transaction txn) {
if (txn != null && txn.isActive()) {
txn.rollback();
}
}
private static void commit(Transaction txn) {
if (txn != null && txn.isActive()) {
txn.commit();
}
}
}
Throwables from guava.
It can be used by this way
List<Campaign> getCampaigns(SessionFactory factory, CampaignDao dao) {
return HibernateSessionUtils.request(
factory,
dao::getCampaigns
);
}
In the dao.getCampaigns() method you can get the session
sessionFactory.getCurrentSession()
You can inject the factory everywhere using Guice.
Other option is to use UnitOfWorkAwareProxyFactory.

Spring-AMQP Transactionnal publish without Exception

I am trying to use a Transactionnal RabbitMQ channel with Spring-AMQP but I want to actually swallow Exceptions to log them and being able to recover them.
Using channelTransacted=true forces the Channel to also join the current transactionManager (Hibernate in my case) and that results in the commit Exception being rethrown out of the #Transactionnal boundaries, resulting in a failure at upper level without being able to catch it and log it.
I also tried to manually attach the publish to the Transaction so it gets executed only after commit succeeded :
public void publishFailSafeAfterSuccessfulTransaction(final String routingKey, final String message) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
try {
rabbitTemplate.convertAndSend(routingKey, message);
} catch (Exception exception) {
logger.error("Error while publishing message to RabbitMQ ");
}
}
});
used in that way :
Entity entity = save(entity);
publishFailSafeAfterSuccessfulTransaction("routingkey", "Entity was updated");
but in that case I cannot use the channelTransacted=true because it will nest the registeringSynchronization inside another registeringSynchronization and fail to be called at all...
Is there a way this can be achieved ?
UPDATE : Ideally I would want to override the RabbitResourceSynchronization that is used in the ConnectionFactoryUtils class, but it is a private class without factory instanciated with
TransactionSynchronizationManager.registerSynchronization(new RabbitResourceSynchronization(resourceHolder, connectionFactory, synched));
The solution I implemented was to do the publishing inside a new transaction after the commit of the main transaction.
The first call :
Entity entity = save(entity);
publishFailSafeAfterSuccessfulTransaction("routingkey", "Entity was updated");
This method register to do the publishing after the main transaction committed.
public void publishFailSafeAfterSuccessfulTransaction(final String routingKey, final String event) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
try {
publishFailSafe(routingKey, event);
} catch (Exception exception) {
//Do some recovering
}
}
});
}
After the main transaction committed, this one will do the publishing. As the channel is transacted, it will commit the message at the commit of that new transaction and fail only that one and errors will be caught in the previous method.
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void publishFailSafe(String routingKey, String event) {
try {
rabbitTemplate.convertAndSend(routingKey.getRoutingKey(), event);
} catch (Exception exception) {
//Do some recovering
}
}

Nested #Transactional and exceptions

i want to perform some operations and store it's result (success or failure) in db.
#Service
public MyService {
#Transactional
public void myOperation() {
try {
otherService.doDatabaseOperationThatMayFail();
repository.writeSuccess()
} catch (Exception e) {
repository.writeFailure()
}
}
}
But if otherService.doDatabaseOperationThatMayFail() itself is marked as #Transactionalor uses such components and any of those methods throws an exception then the whole transaction is marked for rollback. I can't remove #Transactional from all services used by MyService. So how can I store the failure info in my database?

Exception handling in CMT stateless bean

Is it possible to catch an exception in a CMT(Container Managed Transaction) stateless bean?
The code below wont catch any exeption when I tried it. If I use BMT(Bean Managed Transaction), I can catch the exception. But I want to remain with CMT.
#Path("books")
public class BookResource
{
#EJB
private BooksFacade book_facade;
private Books local_book;
#POST
#Consumes({"application/xml", "application/json"})
public Response create(Books entity)
{
try
{
book_facade.create(entity);
} catch (RuntimeException ex)
{
System.out.println("Caught database exception");
}
return Response.status(Response.Status.CREATED).build();
}
public class TXCatcher
{
//#Resource
//UserTransaction tx;
private final static Logger LOG = Logger.getLogger(TXCatcher.class.getName());
#AroundInvoke
public Object beginAndCommit(InvocationContext ic) throws Exception
{
//ic.proceed();
System.out.println("Invoking method: " + ic.getMethod());
try
{
//tx.begin();
Object retVal = ic.proceed();
//tx.commit();
return retVal;
}catch (RollbackException e)
{
LOG.log(Level.SEVERE, "-----------------Caught roolback(in interceptor): {0}", e.getCause());
System.out.println("Invoking method: " + ic.getMethod());
throw new CustomEx("Database error");
}catch (RuntimeException e)
{
LOG.log(Level.SEVERE, "-----------------Caught runtime (in interceptor): {0}", e.getCause());
System.out.println("Invoking method: " + ic.getMethod());
//tx.rollback();
throw new CustomEx("Database error",e.getCause());
//throw new CustomEx("Database error");
}
//return ic.proceed();
}
}
It depends what kind of problem you're trying to catch. You could try an explicit EntiyManager.flush, but depending on your data source isolation level, some errors cannot be caught until transaction commit, and there is no mechanism for catching transaction commit errors for a CMT. If that's the case, your only option is to use BMT (even though you said you don't want to). The only suggestion that might make that more palatable would be to write an EJB interceptor that behaves similarly to CMT (that is, inject UserTransaction into the interceptor, and begin/commit/rollback in the #AroundInvoke).
By placing the following above my function in my BooksFacade class create function, the CMT created a 2nd transaction within the first transaction. When the exception was thrown from the 2nd transaction, my BookResource class create method could catch it. No need for BMT.
#Overide
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void create(Books entity)
{
super.create(entity);
}
I noted that the annotation only works when placed on the individual methods, by placing it on the class itself wont make a difference.

Categories