#Repository
public class MyClass{
public int myFunction(Integer id){
Record x = recordDao.get(id,LockMode.UPGRADE);
int d = x.getProp();
x.setProp(d+1);
recordDao.update(z);
}
}
#Service
#Transactional
public class MyOtherClass{
#Autowired
private MyClass myClass;
public void myOtherFnx(Integer id){
try{
myClass.myFunction(id);
}catch(Exception e){
throw new RuntimeException(e.getMessage());
}
}
}
What happens when new MyOtherClass().myOtherFnx(3) was invoked at same time but at separate request/transactions.
I tried creating runnables and invoke myOtherFunx on separate threads to simulate concurrency. I was expecting to get RuntimeException but
so far I haven't notice getting any runtime exception even if i increase the threads running myOtherFunx to 50.
Can you explain to me why this happens? Or is my assumption that a runtime exception will occur. The case when I am expecting a runtime exception
is when multiple threads invoke myClass.myFunction(3) and the Record object retrieved on these threads are the same, let's say
id=3,prop=4. Since one of them gets the lock first, when threads try to update the record only one should update and others will throw exception since I am catching all Exception.
The first Thread will acquire the lock, and release it on commit or rollback.
While the first Thread is doing its work, the rest of threads will wait for the lock to be released.
After the lock has been released, the next Thread will acquire it, do the updates, commit, and so on...
If you want to get an exception, you will have to use the LockMode UPGRADE_NOWAIT.
Related
I have a job method in a class-annotated #Transactional. This job method calls inner methods for persistence of individual records. If I simulate an error in the following inner update() method somewhere in the middle of my result set processing, I see that all successful records before/after this exception do not get saved after job completion. Why is that? All outside persistence should remain, with the exception of the individual record that failed. The inner update alone has rollbackFor.
#Service("mailService")
#Transactional
#EnableScheduling
public class MailServiceImpl implements MailService {
#Override
#Scheduled(cron = "${mail.cron.pubmed.autosynch.job}")
public void autoSynchPubMedJob() {
//... Fetch result set
for (Result r: resultset) {
try {
pubService.updatePublication(r);
} catch (Exception e) {
// Silently log and continue
log.error("Error on record: ", e);
}
}
}
The updatePublication method, this is the one with rollbackFor:
#Override
#Transactional(readOnly = false, rollbackFor = Exception.class)
public void updatePublication(Publication publication) throws Exception {
dao.update1(..);
dao.update2(..);
// Simulate exception for a specific record for testing
if (publication.getId() == 123) {
throw new Exception("Test Exception");
}
}
Result: no successful data persisted at all at the end of job completion. There should be partial persistence (for other successful records).
When I remove this Exception simulation, all data is successfully persisted at the end. Also, all data is persisted if I remove the inner call's rollbackFor.
Probaby because it uses existing transaction. Try opening a new one with propagation = REQUIRES_NEW.
Note: New transaction won't be opened if you call the method from the same service. You should use either self-reference call or extract logic to another #Service.
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.
I am trying to save exception message in DB table in Catch block but the Exception Transaction is also getting rolled back when the code passes the whole #Transactional method.
Ex:
#javax.transaction.Transactional
public void operationMethod(){
serviceMethod();
}
public void serviceMethod()
try{
}
catch(Exception e){
saveException(e.getMessage());
}
}
//here tried #org.springframework.transaction.annotation.Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = Exception.class) and different combinations with Nested and Required
saveException(String exceptionMessage){
//save it into table logic
}
Still it is giving the "Transaction silently rolled back because it has been marked as rollback-only" exception. Is there any way to force commit only saveException part without save try block and rolling back exception save.
Thanks in Advance
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
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.