Spring-AMQP Transactionnal publish without Exception - java

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
}
}

Related

Prevent Transaction to be rolled back even if the Stored Procedure invocation fails

I have a service class marked with #Transactional. after saving the entities I invoke a Stored Procedure with ApplicationEventPublisher. The Listener is annotated with #TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT). Now the event listener throws an exception while invoking the Stored Procedure, and it rollbacks the entire transaction. Is there a way to prevent this rollback from happening?
I need to execute the entity saving and stored procedure in the same transaction. How can this be achieved?
My Service Class Method, I need this service class method to commit the transaction, even if the stored proc execution fails. When I don't invoke the SP its works as expected.
#Transactional
void saveAndInvokeSP() {
repo.save(entity);
applicationEventPublisher.publishEvent(new SPInvocationEvent());
}
My Event Listener Class
class EventListener {
final EntityManager entityManager;
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(SPInvocationEvent event) {
entityManager.flush();
invokeStoreProcedure(OUTBOUND_STORED_PROC, event);
}
void invokeStoreProcedure(String outProcName, SPInvocationEvent event) {
StoredProcedureQuery outsumSP = entityManager.createStoredProcedureQuery(outProcName);
try {
outsumSP.execute();
} catch (Exception e) {
log.error(e.getMessage());
}
}
}

#Transactional on #Async methods in Spring

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.

Continue with transaction after exception - JPA

I am using JPA with Spring. I am trying to do batch import. If there is problem with batch import then I would like to insert individually, and if this fails also then I would like to save to duplicates table. I wrote a logic for this but I get this error everytime:
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
Mine setting for JPA are like this:
#Bean(name = "dataSource", destroyMethod = "")
public DataSource getDataSource() {
return new JndiDataSourceLookup().getDataSource(props.getDbJndiName());
}
#Bean
public JpaVendorAdapter getHibernateJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean getEntityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean lcemfb = new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(getDataSource());
lcemfb.setPersistenceUnitName("MyPU");
lcemfb.setPackagesToScan("com.project");
lcemfb.setJpaVendorAdapter(getHibernateJpaVendorAdapter());
lcemfb.setJpaProperties(getHibernateProperties());
return lcemfb;
}
#Bean
public Properties getHibernateProperties() {
Properties jpaProperties = new Properties();
jpaProperties.put(DIALECT, "org.hibernate.dialect.Oracle10gDialect");
jpaProperties.put(SHOW_SQL, true);
jpaProperties.put(AUTOCOMMIT, true);
jpaProperties.put(FORMAT_SQL, true);
jpaProperties.put(USE_SQL_COMMENTS, true);
jpaProperties.put(STATEMENT_BATCH_SIZE, 20);
jpaProperties.put(ORDER_INSERTS, true);
jpaProperties.put("hibernate.ejb.entitymanager_factory_name", "MyEM");
return jpaProperties;
}
#Bean
public JpaTransactionManager getTransactionManager() {
return new JpaTransactionManager(getEntityManagerFactoryBean().getObject());
}
#Bean
public PersistenceExceptionTranslationPostProcessor getPersistenceExceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
I get entity manager like this
#PersistenceContext(unitName = "MyPU")
private EntityManager em;
protected EntityManager em() {
return em;
}
my import method is:
#Override
#Transactional
public void importBusinessFile(MultipartFile file)
throws GeneralException, IOException {
// process file
//save batch
dealsRepository.saveBatch(deals);
}
and saveBatch method from repository:
public void saveBatch(List<Deal> list) {
for (Deal deal : list) {
em().persist(deal);
}
try {
em().flush();
em().clear();
} catch (Exception e) {
log.info("Duplicates detected, save individually.", e);
for (Deal deal : list) {
try {
save(deal);
} catch (Exception ex) {
log.error("Problem saving individual deal", e);
// TODO write to duplicates
}
}
}
}
I tried setting dontRollbackOn but I can't get past this exception. I found some other similar threads but none helped me.
In case if you method has #Transactional annotation, occurrence of any exception inside your method marks the surrounding transaction as roll-back.
You can add an attribute for #Transactional annotation to prevent it of rolling back like : #Transactional(noRollbackFor=Exception.class). Spring rollback transaction for all sub type of runtime exceptions.
If you want to do something when you catch you should try to do it in new transaction.But remeber that self invocation in spring not supported , you can't just call transactional method2 from method1 , you should get from spring context current service and call method2.
PROPAGATION_NESTED uses a single physical transaction with multiple
savepoints that it can roll back to. Such partial rollbacks allow an
inner transaction scope to trigger a rollback for its scope, with the
outer transaction being able to continue the physical transaction
despite some operations having been rolled back. This setting is
typically mapped onto JDBC savepoints, so will only work with JDBC
resource transactions. See Spring’s DataSourceTransactionManager.
simple variant :
#Autowired
private ApplicationContext context.
#Override
#Transactional
public void importBusinessFile(MultipartFile file)
throws GeneralException, IOException {
// process file
try{
dealsRepository.saveBatch(deals);
//in case fail-transaction for saveBatch is rollback main transactio is active
}catch(Exception e){
context.getBean(curent serivce).tryReSaveBatch(deals);
//in case fail - transaction for tryReSaveBatchis rollback ,
main transactio is active
}
// main transaction commited
}
#Transactional(propagation = NESTED)
public void saveBatch(List<Deal> list) {
for (Deal deal : list) {
em().persist(deal);
}
}
#Transactional(propagation = NESTED)
public void tryReSaveBatch(List<Deal> list) {
for (Deal deal : list) {
try {
save(deal);
} catch (Exception ex) {
log.error("Problem saving individual deal", e);
// TODO write to duplicates
}
}
}
I only managed to fix this by creating another bean containing batch import method. So after that Spring can intercept the call from this bean and start a new transaction.

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