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() { ... }
}