springguavaevent-bus

guava eventbus post after transaction/commit


I am currently playing around with guava's eventbus in spring and while the general functionality is working fine so far i came across the following problem:

When a user want's to change data on a "Line" entity this is handled as usual in a backend service. In this service the data will be persisted via JPA first and after that I create a "NotificationEvent" with a reference to the changed entity. Via the EventBus I send the reference of the line to all subscribers.

public void notifyUI(String lineId) {
    EventBus eventBus = getClientEventBus();
    eventBus.post(new LineNotificationEvent(lineId));
}

the eventbus itself is created simply using new EventBus() in the background.

now in this case my subscribers are on the frontend side, outside of the @Transactional realm. so when I change my data, post the event and let the subscribers get all necessary updates from the database the actual transaction is not committed yet, which makes the subscribers fetch the old data.

the only quick fix i can think of is handling it asynchronously and wait for a second or two. But is there another way to post the events using guava AFTER the transaction has been committed?


Solution

  • I don't think guava is "aware" of spring at all, and in particular not with its "@Transactional" stuff.

    So you need a creative solution here. One solution I can think about is to move this code to the place where you're sure that the transaction has finished. One way to achieve that is using TransactionSyncrhonizationManager:

    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
               void afterCommit(){
                    // do what you want to do after commit
                    // in this case call the notifyUI method
               }
    });
    
    

    Note, that if the transaction fails (rolls back) the method won't be called, in this case you'll probably need afterCompletion method. See documentation

    Another possible approach is refactoring your application to something like this:

    @Service
    public class NonTransactionalService {
    
       @Autowired 
       private ExistingService existing;
    
       public void entryPoint() {
    
          String lineId = existing.invokeInTransaction(...);
          // now you know for sure that the transaction has been committed
          notifyUI(lineId);
       }
    }
    
    @Service
    public class ExistingService  {
    
       @Transactional
       public String invokeInTransaction(...) {
          // do your stuff that you've done before
       }
    }
    
    

    One last thing I would like to mention here, is that Spring itself provides an events mechanism, that you might use instead of guava's one.

    See this tutorial for example