Controlling inner transaction settings from outer transaction with Spring 2.5 - spring

I'm using Spring 2.5 transaction management and I have the following set-up:
Bean1
#Transactional(noRollbackFor = { Exception.class })
public void execute() {
try {
bean2.execute();
} catch (Exception e) {
// persist failure in database (so the transaction shouldn't fail)
// the exception is not re-thrown
}
}
Bean2
#Transactional
public void execute() {
// do something which throws a RuntimeException
}
The failure is never persisted into DB from Bean1 because the whole transaction is rolled back.
I don't want to add noRollbackFor in Bean2 because it's used in a lot of places which don't have logic to handle runtime exceptions properly.
Is there a way to avoid my transaction to be rolled back only when Bean2.execute() is called from Bean1?
Otherwise, I guess my best option is to persist my failure within a new transaction? Anything else clean I can do?

This is one of the caveats of annotations... your class is not reusable!
If you'd configure your transactions in the XML, if would have been possible.
Assuming you use XML configuration: if it's not consuming expensive resources, you can create another instance of bean2 for the use of the code you specified. That is, you can configure one been as you specified above, and one with no roll back for exception.

Related

Message are not commited (loss) when using #TransactionalEventListener to send a message in a JPA Transaction

Background of the code:
In order to replicate a production scenario, I have created a dummy app that will basically save something in DB in a transaction, and in the same method, it publishEvent and publishEvent send a message to rabbitMQ.
Classes and usages
Transaction Starts from this method.:
#Override
#Transactional
public EmpDTO createEmployeeInTrans(EmpDTO empDto) {
return createEmployee(empDto);
}
This method saves the record in DB and also triggers publishEvent
#Override
public EmpDTO createEmployee(EmpDTO empDTO) {
EmpEntity empEntity = new EmpEntity();
BeanUtils.copyProperties(empDTO, empEntity);
System.out.println("<< In Transaction : "+TransactionSynchronizationManager.getCurrentTransactionName()+" >> Saving data for employee " + empDTO.getEmpCode());
// Record data into a database
empEntity = empRepository.save(empEntity);
// Sending event , this will send the message.
eventPublisher.publishEvent(new ActivityEvent(empDTO));
return createResponse(empDTO, empEntity);
}
This is ActivityEvent
import org.springframework.context.ApplicationEvent;
import com.kuldeep.rabbitMQProducer.dto.EmpDTO;
public class ActivityEvent extends ApplicationEvent {
public ActivityEvent(EmpDTO source) {
super(source);
}
}
And this is TransactionalEventListener for the above Event.
//#Transactional(propagation = Propagation.REQUIRES_NEW)
#TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onActivitySave(ActivityEvent activityEvent) {
System.out.println("Activity got event ... Sending message .. ");
kRabbitTemplate.convertAndSend(exchange, routingkey, empDTO);
}
This is kRabbitTemplate is a bean config like this :
#Bean
public RabbitTemplate kRabbitTemplate(ConnectionFactory connectionFactory) {
final RabbitTemplate kRabbitTemplate = new RabbitTemplate(connectionFactory);
kRabbitTemplate.setChannelTransacted(true);
kRabbitTemplate.setMessageConverter(kJsonMessageConverter());
return kRabbitTemplate;
}
Problem Definition
When I am saving a record and sending a message on rabbitMQ using the above code flow, My messages are not delivered on the server means they lost.
What I understand about the transaction in AMQP is :
If the template is transacted, but convertAndSend is not called from Spring/JPA Transaction then messages are committed within the template's convertAndSend method.
// this is a snippet from org.springframework.amqp.rabbit.core.RabbitTemplate.doSend()
if (isChannelLocallyTransacted(channel)) {
// Transacted channel created by this template -> commit.
RabbitUtils.commitIfNecessary(channel);
}
But if the template is transacted and convertAndSend is called from Spring/JPA Transaction then this isChannelLocallyTransacted in doSend method will evaluate false and commit will be done in the method which initiated Spring/JPA Transaction.
What I found after investigating the reason for message loss in my above code.
Spring transaction was active when I called convertAndSend method, so it was supposed to commit the message in Spring transaction.
For that, RabbitTemplate binds the resources and registers the Synchronizations before sending the message in bindResourceToTransaction of org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.
public static RabbitResourceHolder bindResourceToTransaction(RabbitResourceHolder resourceHolder,
ConnectionFactory connectionFactory, boolean synched) {
if (TransactionSynchronizationManager.hasResource(connectionFactory)
|| !TransactionSynchronizationManager.isActualTransactionActive() || !synched) {
return (RabbitResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory); // NOSONAR never null
}
TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolder);
resourceHolder.setSynchronizedWithTransaction(true);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new RabbitResourceSynchronization(resourceHolder,
connectionFactory));
}
return resourceHolder;
}
In my code, after resource bind, it is not able to registerSynchronization because TransactionSynchronizationManager.isSynchronizationActive()==false. and since it fails to registerSynchronization, spring commit did not happen for the rabbitMQ message as AbstractPlatformTransactionManager.triggerAfterCompletion calls RabbitMQ's commit for each synchronization.
What problem I faced because of the above issue.
Message was not committed in the spring transaction, so the message lost.
As resource was added in bindResourceToTransaction, this resource remained bind and did not let add the resource for any other message to send in the same thread.
Possible Root Cause of TransactionSynchronizationManager.isSynchronizationActive()==false
I found the method which starts the transaction removed the synchronization in triggerAfterCompletion of org.springframework.transaction.support.AbstractPlatformTransactionManager class. because status.isNewSynchronization() evaluated true after DB opertation (this usually not happens if I call convertAndSend without ApplicationEvent).
private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) {
if (status.isNewSynchronization()) {
List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
TransactionSynchronizationManager.clearSynchronization();
if (!status.hasTransaction() || status.isNewTransaction()) {
if (status.isDebug()) {
logger.trace("Triggering afterCompletion synchronization");
}
// No transaction or new transaction for the current scope ->
// invoke the afterCompletion callbacks immediately
invokeAfterCompletion(synchronizations, completionStatus);
}
else if (!synchronizations.isEmpty()) {
// Existing transaction that we participate in, controlled outside
// of the scope of this Spring transaction manager -> try to register
// an afterCompletion callback with the existing (JTA) transaction.
registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations);
}
}
}
What I Did to overcome on this issue
I simply added #Transactional(propagation = Propagation.REQUIRES_NEW) along with on #TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) in onActivitySave method and it worked as a new transaction was started.
What I need to know
Why this status.isNewSynchronization in triggerAfterCompletion method when using ApplicationEvent?
If the transaction was supposed to terminate in the parent method, why I got TransactionSynchronizationManager.isActualTransactionActive()==true in Listner class?
If Actual Transaction Active, was it supposed to remove the synchronization?
In bindResourceToTransaction, do spring AMQP assumed an active transaction without synchronization? if the answer is yes, why not to synchronization. init if it is not activated?
If I am propagating a new transaction then I am losing the parent transaction, is there any better way to do it?
Please help me on this, it is a hot production issue, and I am not very sure about the fix I have done.
This is a bug; the RabbitMQ transaction code pre-dated the #TransactionalEventListener code, by many years.
The problem is, with this configuration, we are in a quasi-transactional state, while there is indeed a transaction in process, the synchronizations are already cleared because the transaction has already committed.
Using #TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) works.
I see you already raised an issue:
https://github.com/spring-projects/spring-amqp/issues/1309
In future, it's best to ask questions here, or raise an issue if you feel there is a bug. Don't do both.

JOOQ execution listener does not catch exception

I'm trying to implement a generic solution for optimized locking. What I want to achieve is to have a specific piece of code run when record's version changes. I have it implemented as an ExecuteListener instance that looks for DataChangedException. It's registered as a Spring bean.
class LockingListener : DefaultExecuteListener() {
override fun exception(ctx: ExecuteContext) {
val exception = ctx.exception()
if (exception is DataChangedException) {
ctx.exception(IllegalStateException("Accessed data has been altered mid-operation."))
}
}
}
#Configuration
class JooqConfig {
#Bean
fun lockingListenerProvider() = DefaultExecuteListenerProvider(LockingListener())
}
I had a breakpoint set in org.jooq.impl.ExecuteListeners#get and it does look like it gets picked up alongside LoggerListener and JooqExceptionTranslator.
When I try to run a test case though, DataChangedException does not get picked up on UpdateableRecord#update and I get the following stacktrace instead, no IllegalStateException in sight.
org.jooq.exception.DataChangedException: Database record has been changed or doesn't exist any longer
at org.jooq.impl.UpdatableRecordImpl.checkIfChanged(UpdatableRecordImpl.java:540)
at org.jooq.impl.UpdatableRecordImpl.storeMergeOrUpdate0(UpdatableRecordImpl.java:349)
at org.jooq.impl.UpdatableRecordImpl.storeUpdate0(UpdatableRecordImpl.java:241)
at org.jooq.impl.UpdatableRecordImpl.access$100(UpdatableRecordImpl.java:89)
at org.jooq.impl.UpdatableRecordImpl$2.operate(UpdatableRecordImpl.java:232)
at org.jooq.impl.RecordDelegate.operate(RecordDelegate.java:149)
at org.jooq.impl.UpdatableRecordImpl.storeUpdate(UpdatableRecordImpl.java:228)
at org.jooq.impl.UpdatableRecordImpl.update(UpdatableRecordImpl.java:165)
Debugging shows that LockingListener#exception does not even get entered into.
That exception is not part of the ExecuteListener lifecycle, i.e. the lifecycle that deals with interactions with the JDBC API. In other words, it's not a SQLException, it happens higher up the stack. Use the RecordListener.exception() callback, instead.

Spring #transactional with #async Timeout value is not working

I have created an asynchronous service for a long running stored procedure call. Things work good but the transaction is not getting timed out after the specified value given in the timeout attribute of the transactional annotation..The structure of the code is given below (not the real one...just skeleton...ignore semantics/syntax)
//asynchronous service
#override
#async("myCustomTaskExecutor")
#Transactional(rollbackfor=Exception.class,timeout=600)
public void serviceMethod(){
//repository method is invoked.
repository.callStoredProcedure();
}
//Repository method in the Repository class
#Transactional(rollbackfor=Exception.class,timeout=600)
public void callStoredProcedure(){
//Stored procedure is called from the private method using hibernate doWork implementation.
privateCallmethod();
}
private void privateCallmethod() throws ApplicationException{
Session session = null;
try{
session = entityManager.unwrap(Session.class);
session.doWork(new Work(){
#Override
public void execute(Connection connection) throws SQLException {
OracleCallableStatement statement =null;
try{
//using hibernate 4.x and ref cursors are used...so went on with this approach..
//suggest if there is some better approach.
String sqlString =“{begin storProcName(?,?)}”;
statement = connection.prepareCall(sqlString);
statement.setInt(1,5);
statement.setString(2,“userName5”);
statement.executeUpdate();
}
catch(Exception e){
throw RunTimeException(e.getMessage);
}
finally{
if(statement != null)
statement.close();
}
}
}
});
}
catch(Exception e){
throw ApplicationException(e.getMessage);
}
//Not using Final block to close the session.Is it an issue ?
}
delay is happening in the stored procedure side(Thread.sleep(700) are not used) yet transaction is not timed out...
Questions :
I guess #Transactional is enough on the service method alone...give little bit insight on correct approach of using #Transactional annotation
for this code setup.
Will the #Transactional works for the JDBC calls inside the doWork Interface implementation...is that whats the issue is ?
Some article suggest to use oracle.jdbc.readTimeout or setQueryTimeout in the CallableStatement... Is it the right way to achieve this.
Kindly point out the mistakes and explain the causes
If #Transactional Annotated method is not the entry point to the class, it will not be transactional unless you enable load time weaving (Spring default is Compile time weaving) https://stackoverflow.com/a/17698587/6785908
You should invoke callStoredProcedure() from outside this class, then it will be transactional. If you invoke serviceMethod() which in turn invokes callStoredProcedure(), then it will not be transactional
I used setQueryTimeout() approach to resolve the issue as #Transactional timeout does not work with the hibernate dowork() method...I guess its due to the hibernate work executes in different thread and it low level JDBC methods to invoke the store procedures...
NOTE: This particular application uses very spring 3.x version and hibernate 4.x with JPA 2.0 spec...little outdated versions

Why Grails/Spring transactional behavior not working in this case?

I have a grails (2.5.2) app, with a mysql and a NoSQL interaction. There's a main/principal service method that call 2 other methods:
class mainService {
static transactional = false
NoSQLDataAccessService noSQLDataAccessService
// main/principal method
#Transactional
void save(json){
// (1) creating domain entities from json
addNewDomainEntities(entities)
// (2)
noSQLDataAccessService.set(json)
}
#Transactional
void addNewDomainEntities(entities){
// save the entities in a mysql schema and use save(flush:true)
// because i need the generated id's
}
}
As you can see, this mainService creates new domain entities (1), flushing the session to get the id's. Then, i call other service method (2) that store the json in a NoSQL schema:
class NoSQLDataAccessService(){
static transactional = false
void set(json){
try{
// save the json in a NoSQL schema
} catch(Exception ex){
// if fails, i log the exception and throws it again
throws ex
}
}
}
But, sometimes the noSQLDataAccessService.set() fails by external causes and the entities created before still persist in the mysql db.(this is the problem)
The save method, that contains all this program execution, is marked like #Transactional, so if the noSQLDataAccessService.set() throws an exception, all the changes made should be not applied because the rollback. I'm right?
You probably have to throw an RuntimeException, not an Exception, to force a rollback as per this StackOverflow conversation. Instead of:
throws ex
you might try:
throw new RuntimeException(ex)
Further, I would recommend you be explicit about your transaction isolation. Perhaps something like:
#Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)

Some transaction propagations not working with Spring/Hibernate 4

I'm in the process of upgrading an application to Hibernate 4.2 from 3.3. We're also using Spring 3.1.3 (which we can't/won't update at this time).
Some of my unit tests are now failing with
org.hibernate.HibernateException: No Session found for current thread
in the SpringSessionContext. This is not an issue with the <tx:annotation-driven /> being defined in the wrong context, or a case of missing CGLIB libraries. Most of the tests do work, which means that in most cases, the transaction proxying is working.
The cases where it is now failing seem to be around the use of NOT_SUPPORTED, NEVER, and SUPPORTED propagation types. For whatever reason the SpringSessionContext doesn't create a session in these cases.
Our use cases sometimes require that transactional boundaries don't strictly line up with method boundaries, and that sessions sometimes outlive transactions. In the Spring 3/Hibernate 3 case, the session context was bound to a thread local, and a call to SessionFactory.getCurrentSession() would return a session instance even if a transaction was not started. This is the behavior that I am looking to still have in the Hibernate 4 case.
Does anyone know a workaround for this? It's tough to align Session boundaries with a conversation instead of a transaction if Spring refuses to create a session without a valid transaction. A Session and its persistence context shouldn't be tied to an open transaction.
Worked around this issue by implementing a CurrentSessionContext that is a wrapper around a SpringSessionContext and borrowing some of the code changes that made it into Spring Framwork 4+:
public class ClassLoaderSpringSessionContext implements CurrentSessionContext {
private final SessionFactoryImplementor sessionFactory;
private final SpringSessionContext sessionContext;
public ClassLoaderSpringSessionContext(final SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory; // This is actually some class loading logic that isn't important to this transaction problem.
this.sessionContext = new SpringSessionContext(this.sessionFactory);
}
#Override
public Session currentSession() throws HibernateException {
try {
return sessionContext.currentSession();
} catch (HibernateException e) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager
.registerSynchronization(new SpringSessionSynchronization(sessionHolder,
this.sessionFactory));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
return session;
} else {
throw new HibernateException(
"Could not obtain transaction-synchronized Session for current thread");
}
}
}
}
SpringSessionSynchronization was a package private class in Spring 4, so I also had to pull a version of that as a private inner class to ClassLoaderSpringSessionContext.
Hope this helps someone else.

Resources