Thanks for your response, very helpful.
I'm experimenting with [1]. I'm still figuring out which transaction
level I need to set and making sure that my TransactionProvider
implements the desired behavior.
A few things I figured might be worth sharing:
1. I'm using a counter and plan to only commit the top-level
transaction. I'm keeping the counter in ctx.configuration().data()
(which is very similar to DefaultTransactionProvider's behavior,
except that has a Stack). It looks like the
DefaultTransactionProvider's savepoints() method is not thread safe.
Do TransactionProviders need to care about concurrent calls to
begin()? I'm guessing it's unlikely that a transaction gets shared
between different threads, but I'm no expert.
2. I don't think DefaultTransactionProvider needs to have a Connection
instance variable. The brace() method could just use the connection
already stored in the map?
3. What is the purpose of the data() map in TransactionContext?
4. I was initially not creating a new jooq context inside the
transaction (DSL.using(configuration)). I have the feeling the API
would be less error prone if TransactionalRunnable and
TransactionalCallable took a DSLContext instead of a configuration
(the configuration object would be available via the
context.configuration method if needed).
Alok
[1]:
class MyTransactionProvider implements TransactionProvider {
private final ConnectionProvider provider;
public MyTransactionProvider(ConnectionProvider provider) {
this.provider = provider;
}
@Override
public final void begin(TransactionContext ctx) {
Map<Object, Object> data = ctx.configuration().data();
int counter = (int)data.compute("counter", (k, v) -> (v == null) ?
1 : (int) v + 1);
if (counter > 1) {
// We are in a sub-transaction, don't do anything.
return;
}
try {
Connection connection = provider.acquire();
if (data.get("connection") != null) {
throw new DataAccessException("begin failed: connection wasn't null");
}
if (connection.getAutoCommit() != true) {
// If auto-commit was false, how do we know what is the
beginning of a transaction?
throw new DataAccessException("begin failed: was expecting
autocommit to be true");
}
data.put("connection", connection);
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new DataAccessException("begin failed", e);
}
}
@Override
public final void commit(TransactionContext ctx) {
Map<Object, Object> data = ctx.configuration().data();
int counter = (int)data.compute("counter", (k, v) -> (v == null) ?
1 : (int) v - 1);
if (counter > 0) {
// We only commit the top-level transaction
return;
}
try {
Connection connection = (Connection)data.remove("connection");
connection.commit();
connection.setAutoCommit(true);
provider.release(connection);
} catch (SQLException e) {
throw new DataAccessException("commit failed", e);
}
}
@Override
public final void rollback(TransactionContext ctx) {
// Rollbacks can happen in any sub-transaction.
try {
Map<Object, Object> data = ctx.configuration().data();
Connection connection = (Connection)data.remove("connection");
connection.rollback();
connection.setAutoCommit(true);
// reset the counter. Do we care about concurrency?
data.put("counter", 0);
provider.release(connection);
} catch (SQLException e) {
throw new DataAccessException("rollback failed", e);