Search code examples
javaspringannotations

debugging Transactional annotation in Java Spring


Updating an existing piece of code as shown below:

@Override
@Transactional
public void update(SomeObject someObj) throws BusinessServiceException {
    ...
    someObjectRepository1.update(someObj);
    someObjectRepository2.create(someObj);

    //New code below:
    someObjectRelatedService.doStuff(someObj);  // <--new code
    //Throws error, object does not exist in someObjectRepository2
}

The issue here is that someObjectRelatedService.doStuff needs the latest and most updated version of someObjectRepository2 - but the transactional annotation prevents that from happening, it updates the repository AFTER the method has fully ran. I was able to verify by using a debugger and running a database call in between.

What would be the best way to approach this situation? Is it solely to extract the someObjectRelatedService call outside the transactional method?


Solution

  • In essence, you need to move the boundary of your transaction. While manually flushing the transaction is an option, I would not recommend to do so.

    The easiest fix, if the semantics of SomeObjectRepository2#create(...) permit it, would be to annotate this method with @Transactional(isolation = Propagation.REQUIRES_NEW).

    If this should not be possible, I propose the following minor redesign. First, we create a new class with one method, which should execute the code related to the data that is needed:

    @Component
    public class NewClass {
        final SomeObjectRepository2 someObjectRepository2;
    
        @Autowired
        public NewClass(final SomeObjectRepository2 someObjectRepository2) {
            this.someObjectRepository2 = someObjectRepository2;
        }
    
        @Transactional(isolation = Propagation.REQUIRES_NEW) // enforces new transaction
        public WhateverNeedsToBereturned newMethod(WhateverType someObject) {
            return someObjectRepository2.create(someObj);
        }
    }
    

    And then we rewrite the existing code to use the new class:

    @Override
    @Transactional
    public void update(SomeObject someObj) throws BusinessServiceException {
        ...
        someObjectRepository1.update(someObj);
        newClassInstance.newMethod(someObj);
    
        //New code below:
        someObjectRelatedService.doStuff(someObj); 
        //Throws error, object does not exist in someObjectRepository2
    }
    

    Note that you have to create a new class in order for this to work. Method calls through the this-reference are not proxied and thus the @Transactional annotation does not take effect.

    Note further that newClassInstance must be injected through the DI container for the same reason.