8

I have a Spring application which updates particular entity details in MySQL DB using a @Transactional method, And within the same method, I am trying to call another endpoint using @Async which is one more Spring application which reads the same entity from MySql DB and updates the value in redis storage.

Now the problem is, every time I update some value for the entity, sometimes its updated in redis and sometimes it's not.

When I tried to debug I found that sometimes the second application when it reads the entity from MySql is picking the old value instead of updated value.

Can anyone suggest me what can be done to avoid this and make sure that second application always picks the updated value of that entity from Mysql?

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • 11
    The transaction is only committed after the `@Transactional` method finishes. So depending on how fast the `@Async` method executes the transaction might (or not) have been committed. Instead of calling the `@Async` method from the `@Transactional` method create another class that first calls the `@Transactional` method and then the `@Async` method for consistent behavior. – M. Deinum Aug 14 '18 at 05:52
  • What has that comment to do with my comment/answer? It doesn't add anything. – M. Deinum Aug 14 '18 at 07:06
  • @M.Deinum by default Transactional(propagation = Propagation.REQUIRED). It means by calling the method it starts new thread, So compiler doesn't wait for completion and goes to next line which is Async method. I think it doesn't guarantee in order execution. – Pasha Aug 14 '18 at 07:10
  • And how is that different from my comment, that is EXACTLY what I explain. Also your answer, concurrency management, won't help with that. It will still read the, possible, wrong value. – M. Deinum Aug 14 '18 at 07:11
  • Your solution doesn't supports the interrupt which is needed! – Pasha Aug 14 '18 at 07:14
  • Yes it does... Because the second thread is launched AFTER the transaction commit. Instead of trying to be rude you might want to read my comment which clearly states that you should FIRST call the `@Transactional` method and after that call the `@Async` method. You shouldn't call the `@Async` method in the `@Tranactional` method because then you loose the guarantee. – M. Deinum Aug 14 '18 at 07:37
  • @Pasha your mention of the "compiler" waiting is worrying. I'm not convinced your comments make any sense. – Boris the Spider Aug 21 '18 at 17:21
  • So Why? @BoristheSpider – Pasha Aug 25 '18 at 05:28

2 Answers2

22

The answer from M. Deinum is good but there is still another way to achieve this which may be simpler for you case, depending on the state of your current application.

You could simply wrap the call to the async method in an event that will be processed after your current transaction commits so you will read the updated entity from the db correctly every time.

Is quite simple to do this, let me show you:

import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

 @Transactional
public void doSomething() {

    // application code here

    // this code will still execute async - but only after the
    // outer transaction that surrounds this lambda is completed.
    executeAfterTransactionCommits(() -> theOtherServiceWithAsyncMethod.doIt());

    // more business logic here in the same transaction
}

private void executeAfterTransactionCommits(Runnable task) {
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        public void afterCommit() {
            task.run();
        }
    });
}

Basically what happens here is that we supply an implementation for the current transaction callback and we only override the afterCommit method - there are others methods there that might be useful, check them out. And to avoid typing the same boilerplate code if you want to use this in other parts or simply make the method more readable I extracted that in a helper method.

  • 1
    Awesome solution! Made my day – Max Binnewies Jun 04 '19 at 04:59
  • @cristian.andreisi.stan `TransactionSynchronization` is an interface. What am I supposed to do for remaining methods? – Arun Gowda Oct 12 '20 at 13:22
  • @ArunGowda Well it is an interface, that is why you only implement the method that you are interested in. For example: this method executeAfterTransactionCommits will register the task afterCommit(). If we would like to execute some code before the commit we could simple create a method executeBeforeTransactionCommits where we implement the method beforeCommit from the TransasctionSynchronization. That only depends on you, what you want to achieve. You can implement more methods, not only one. I only offered an example for this question but your implementation can look a look different – cristian.andrei.stan Oct 19 '20 at 18:06
  • What I meant was, the anonymous class(implementation) will force you to implement all the methods. Do you mean leave the method body empty for remaining methods? – Arun Gowda Oct 20 '20 at 04:24
  • @cristian.andrei.stan I have got a question here. Suppose if I have 4 methods with annotation Transactional and for one of them I set the afterCommit method. Would not that that affect all the 4 methods i.e. the logic in afterCommit will be triggered when transaction commits for all the 4 methods? – uneq95 Mar 31 '21 at 10:13
  • 1
    @uneq95 Of course not, the callback that you add will be linked only to the current transaction. You can check yourself the javadoc of the registerSynchronization method (and also the impleentation) it says: "Register a new transaction synchronization for the current thread.". So of course it cannot affect what happens in other methods and more than that, you can register a synchronization by a condition. Think of it just like a piece of code that runs after the current transaction in commited. – cristian.andrei.stan Apr 03 '21 at 15:13
  • 1
    @ArunGowda Hmm, I did not noticed your second question. Well you are righ that if you use an interface that declares methods and you create an anonymous class from that interface (what is happening here) you would be forced to implement all the methods defined in that interface, obviously. However if you look at the actual TransactionSynchronization code or javadoc you will see that you don't have to because all the methods are declared as default. So they are not declarations that need to be implemented, they are in fact empty method definitions that can be overridden by you. – cristian.andrei.stan Apr 03 '21 at 15:29
  • 2
    This here is a masterpiece of a solution! – Alain Cruz Sep 08 '21 at 18:36
  • For anyone that can mistake this solution for using threads it does not, It just make use of `Runnable` functional interface – Youans Feb 10 '23 at 13:38
3

The solution is not that hard, apparently you want to trigger and update after the data has been written to the database. The @Transactional only commits after the method finished executing. If another @Async method is called at the end of the method, depending on the duration of the commit (or the actual REST call) the transaction might have committed or not.

As something outside of your transaction can only see committed data it might see the updated one (if already committed) or still the old one. This also depends on the serialization level of your transaction but you generally don't want to use an exclusive lock on the database for performance reason.

To fix this the @Async method should not be called from inside the @Transactional but right after it. That way the data is always committed and the other service will see the updated data.

@Service
public class WrapperService {

    private final TransactionalEntityService service1;
    private final AsyncService service2;

    public WrapperService(TransactionalEntityService service1, AsyncService service2) {
        this.service1=service1;
        this.service2=service2;
    }

    public updateAndSyncEntity(Entity entity) {
       service1.update(entity); // Update in DB first
       service2.sync(entity); // After commit trigger a sync with remote system
    }
}

This service is non-transactional and as such the service1.update which, presumable, is @Transactional will update the database. When that is done you can trigger the external sync.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • 3
    You need to be wary of outer transactional scopes - adding a never propagate annotation will prevent that from being an issue in the future. Otherwise someone changing the code to add an enclosing tx will cause the same unpredictable behaviour - which maybe not be detected. – Boris the Spider Aug 21 '18 at 17:23