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
?