Proper way to use multiple transactions in a single class

I'm using Spring Boot. I can use @Transactional to force transaction on a method. Sometimes I need for some method to use two or more transactions.

Naive approach wouldn't work:

public void doActions() {
    doAction1();
    doAction2();
}

@Transactional
void doAction1() { ... }

@Transactional
void doAction2() { ... }

because Spring uses proxies to implement transactions.

Usually I've used the following approach:

@Autowired
private ThisService self;

public void doActions() {
    self.doAction1();
    self.doAction2();
}

@Transactional
void doAction1() { ... }

@Transactional
void doAction2() { ... }

It worked, but in Spring 2.6.0 this circular dependency causes application to fail to start with scary error unless I set spring.main.allow-circular-references to true.

I don't really understand the reason why circular references are bad. But apparently Spring Boot developers want to discourage this kind of design, so, I guess, I better follow their advice.

Another approach is to use transaction manager and programmatically call transaction api:

@Autowired
private TransactionTemplate transactionTemplate;

public void doActions() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) {
                doAction1();
            }
        });
        transactionTemplate.execute(status -> {
            doAction2();
            return null;
        });

It works but it's a little bit verbose and ugly in my opinion.

Is there any other approaches I missed? Should I just set spring.main.allow-circular-references to true? I'm afraid that developers will make those circular references thing unsupported in the future and I'd need to rework my application because of that.


Yes. I agree it is ugly. You can create a service that is just responsible to execute some codes in a transaction . Same idea as TransactionTemplate but it uses @Transational to manage the transaction.

@Service
public class TransactionExecutor {

    @Transactional(propagation = Propagation.REQUIRED)
    public <T> T execute(Supplier<T> action) {
        return action.get();
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void execute(Runnable action) {
        action.run();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public <T> T executeInNewTx(Supplier<T> action) {
        return action.get();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void executeInNewTx(Runnable action) {
        action.run();
    }

}

And then inject it to use it :

@Service
public class FooService {
    
    @Autowired
    private TransactionExecutor txExecutor;


    public void doActions() {
        txExecutor.execute(()->doAction1());
        txExecutor.execute(()->doAction2());
    }

    void doAction1() { ... }

    void doAction2() { ... }

}