With Spring, by default transactions use Propogation.REQUIRED, but this seems like a rather odd choice to me. If we ignore the existence of transactions, then it's a very normal pattern to catch an exception, and implement a fallback. E.g. in it's very basic form:
public void doSomethingFirst() {}
public void doSomethingElse() {}
public void doSomethingWithFallback() {
this.doSomething();
try {
this.doSomethingElse();
} catch (Exception e) {
this.fallback();
}
}
However, Propagation.REQUIRED breaks this:
public void doSomethingFirst() { }
@Transactional(propagation = Propagation.REQUIRED)
public void doSomethingElse() { }
@Transactional(propagation = Propagation.REQUIRED)
public void doSomethingWithFallback() {
this.doSomethingFirst();
try {
this.doSomethingElse();
} catch (Exception e) {
this.fallback();
}
}
Now if doSomethingElse fails, it will mark the transaction as rollback only. Even though we have a perfectly fine fallback, the entire transaction will be rolled back and there's nothing we can do to stop this.
The "fix" for this is to use NESTED instead of REQUIRED (at least in the doSomethingElse):
public void doSomethingFirst() { }
@Transactional(propagation = Propagation.NESTED)
public void doSomethingElse() { }
@Transactional(propagation = Propagation.NESTED)
public void doSomethingWithFallback() {
this.doSomethingFirst();
try {
this.doSomethingElse();
} catch (Exception e) {
this.fallback();
}
}
Now, we're using savepoints, and if doSomethingElse fails it will only rollback doSomethingElse.
To me it looks like NESTED is the behaviour that we almost always want, and I'm struggling to come up with any use case where REQUIRED would be preferred. REQUIRED prohibits the caller from recovering from errors, which is normally a bad idea.
But given that REQUIRED is the default, and that NESTED is barely ever used, surely there must be some reason why we should use REQUIRED over NESTED.
In what cases should we prefer REQUIRED over NESTED?