I've been struggling with this for awhile. I dislike Jon Skeet's answer because a developer (i.e. me) might accidentally forget to call commitOnClose()
. I want a way for the developer to be forced to call either commit() or rollback() when he leaves the block of code.
Lambda's and checked exceptions don't play nice together so a proper solution took a bit of puzzling, but eventually me and a coworker of mine came up with a piece of code that allows you to work like this:
TransactionEnforcer.DbResult<String> result = transactionEnforcer.execute(db -> {
try {
db.someFunctionThatThrowsACheckedException();
} catch (TheException e) {
return failure("fallback value");
}
return success(db.getAFancyValue());
});
result.ifPresent(v -> System.out.println(v));
Note how you can return values, can check if the code succeeded or not, and java's codepath return check enforces you to always be explicit about whether the code should be committed or not.
It is implemented using the code below:
package nl.knaw.huygens.timbuctoo.database;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
public class TransactionEnforcer {
private final Supplier<DbClass> dbClassFactory;
public TransactionEnforcer(Supplier<DbClass> dbClassFactory) {
this.dbClassFactory = dbClassFactory;
}
public <U> DbResult<U> execute(Function<DbClass, DbResult<U>> actions) {
DbClass db = dbClassFactory.get();
try {
DbResult<U> result = actions.apply(db);
if (result.isSuccess()) {
db.close(true);
} else {
db.close(false);
}
return result;
} catch (RuntimeException e) {
db.close(false);
throw e;
}
}
public static class DbResult<T> {
private final Optional<T> value;
private final boolean success;
private DbResult(T value, boolean success) {
this.value = Optional.of(value);
this.success = success;
}
public static <T> DbResult<T> success(T value) {
return new DbResult<T>(value, true);
}
public static <T> DbResult<T> success() {
return new DbResult<T>(null, true);
}
public static <T> DbResult<T> failure(T value) {
return new DbResult<T>(value, false);
}
public static <T> DbResult<T> failure() {
return new DbResult<T>(null, false);
}
public boolean isSuccess() {
return success;
}
public Optional<T> getValue() {
return value;
}
}
}
(I leave the DbClass as an exercise to the reader)