micronaut-projects / micronaut-data

Ahead of Time Data Repositories
Apache License 2.0
469 stars 198 forks source link

Transaction timeout support for regular JDBC transactions #3238

Open lightbody opened 6 days ago

lightbody commented 6 days ago

Feature description

The Micronaut Data Transaction manager has code that wires up timeout attributes on the @TransactionalAdvice / @Transactional annotations up through the transaction definition, but then it doesn't do anything if you're using the basic JDBC transaction manager. It does seem to plumb through for Hibernate, Mongo, and Spring, but not the vanilla JDBC one.

My workaround on 3.x is to proxy the DataSource like so:

public class TimeoutAwareDataSource implements DataSource {
    private final DataSource delegate;

    public TimeoutAwareDataSource(DataSource delegate) {
        this.delegate = delegate;
    }

    @Override
    public Connection getConnection() throws SQLException {
        final Connection connection = delegate.getConnection();
        return new TimeoutAwareConnection(connection, this);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        final Connection connection = delegate.getConnection(username, password);
        return new TimeoutAwareConnection(connection, this);
    }

    // a bunch of other proxied methods
}
import io.micronaut.transaction.jdbc.DataSourceUtils;
//snip

public class TimeoutAwareConnection implements Connection {
    private final Connection inner;
    private final DataSource dataSource;

    public TimeoutAwareConnection(Connection inner, DataSource dataSource) {
        this.inner = inner;
        this.dataSource = dataSource;
    }

    @Override
    public Statement createStatement() throws SQLException {
        final Statement statement = inner.createStatement();
        DataSourceUtils.applyTransactionTimeout(statement, dataSource);
        return statement;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        final PreparedStatement preparedStatement = inner.prepareStatement(sql);
        DataSourceUtils.applyTransactionTimeout(preparedStatement, dataSource);
        return preparedStatement;
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        final CallableStatement callableStatement = inner.prepareCall(sql);
        DataSourceUtils.applyTransactionTimeout(callableStatement, dataSource);
        return callableStatement;
    }

    // a bunch of other proxied methods
}

By doing this, we are seeing timeouts actually work on the 3.x line. We plan to upgrade to 4.x soon and this will have to change because DataSourceUtils doesn't even exist in that code, but it'll be the same approach until this capability is natively introduced.

I might also suggest a bit of docs on timeouts, since it's not really covered today.