JOOQ & транзакции
Я читал о транзакциях и jooq, но я изо всех сил стараюсь понять, как реализовать его на практике.
Скажем, я предоставляю JOOQ пользовательский ConnectionProvider
, который использует пул соединений с автокомбитом, установленным в false.
Реализация примерно:
@Override public Connection acquire() throws DataAccessException {
return pool.getConnection();
}
@Override public void release(Connection connection) throws DataAccessException {
connection.commit();
connection.close();
}
Как я могу обернуть два запроса jooq в одну транзакцию?
Это легко с помощью DefaultConnectionProvider, потому что есть только одно соединение - но с пулом я не уверен, как это сделать.
Ответы
Ответ 1
jOOQ 3.4 API транзакций
В jOOQ 3.4 для абстрактных над JDBC, Spring или менеджерами транзакций JTA был добавлен DSL.using(configuration)
.transaction(ctx -> {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
});
Или с синтаксисом pre-Java 8
DSL.using(configuration)
.transaction(new TransactionRunnable() {
@Override
public void run(Configuration ctx) {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
}
});
Идея состоит в том, что выражение lambda (или анонимный класс) формирует транзакционный код, который:
Выполняется при нормальном завершении
Возврат к исключению
можно использовать для переопределения поведения по умолчанию, которое реализует транзакции nestable через JDBC с помощью .
В текущей документации показан пример использования Spring для обработки транзакций:
Этот пример, по существу, сводится к использованию Spring TransactionAwareDataSourceProxy
<!-- Using Apache DBCP as a connection pooling library.
Replace this with your preferred DataSource implementation -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
init-method="createDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/maven-test" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<!-- Using Spring JDBC for transaction management -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionAwareDataSource"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg ref="dataSource" />
</bean>
<!-- Bridging Spring JDBC data sources to jOOQ ConnectionProvider -->
<bean class="org.jooq.impl.DataSourceConnectionProvider"
name="connectionProvider">
<constructor-arg ref="transactionAwareDataSource" />
</bean>
Ниже приведен пример запуска из GitHub:
Хотя я лично не рекомендовал бы этого, некоторые пользователи успели заменить часть Spring DI на Guice и обрабатывать транзакции с Guice. В этом примере использования также есть пример протекания проверки интеграции в GitHub:
Ответ 2
Это, вероятно, не самый лучший способ, но, похоже, он работает. Предостережение заключается в том, что это не метод release
, а commit
, который закрывает соединение и возвращает его в пул, что довольно запутывает и может привести к проблемам, если какой-то код "забывает" совершить...
Таким образом, код клиента выглядит так:
final PostgresConnectionProvider postgres =
new PostgresConnectionProvider("localhost", 5432, params.getDbName(), params.getUser(), params.getPass())
private static DSLContext sql = DSL.using(postgres, SQLDialect.POSTGRES, settings);
//execute some statements here
sql.execute(...);
//and don't forget to commit or the connection will not be returned to the pool
PostgresConnectionProvider p = (PostgresConnectionProvider) sql.configuration().connectionProvider();
p.commit();
И ConnectionProvider:
public class PostgresConnectionProvider implements ConnectionProvider {
private static final Logger LOG = LoggerFactory.getLogger(PostgresConnectionProvider.class);
private final ThreadLocal<Connection> connections = new ThreadLocal<>();
private final BoneCP pool;
public PostgresConnectionProvider(String serverName, int port, String schema, String user, String password) throws SQLException {
this.pool = new ConnectionPool(getConnectionString(serverName, port, schema), user, password).pool;
}
private String getConnectionString(String serverName, int port, String schema) {
return "jdbc:postgresql://" + serverName + ":" + port + "/" + schema;
}
public void close() {
pool.shutdown();
}
public void commit() {
LOG.debug("Committing transaction in {}", Thread.currentThread());
try {
Connection connection = connections.get();
if (connection != null) {
connection.commit();
connection.close();
connections.set(null);
}
} catch (SQLException ex) {
throw new DataAccessException("Could not commit transaction in postgres pool", ex);
}
}
@Override
public Connection acquire() throws DataAccessException {
LOG.debug("Acquiring connection in {}", Thread.currentThread());
try {
Connection connection = connections.get();
if (connection == null) {
connection = pool.getConnection();
connection.setAutoCommit(false);
connections.set(connection);
}
return connection;
} catch (SQLException ex) {
throw new DataAccessException("Can't acquire connection from postgres pool", ex);
}
}
@Override
//no-op => the connection won't be released until it is commited
public void release(Connection connection) throws DataAccessException {
LOG.debug("Releasing connection in {}", Thread.currentThread());
}
}
Ответ 3
Самый простой способ (я нашел) использовать Spring Транзакции с jOOQ, приведен здесь: http://blog.liftoffllc.in/2014/06/jooq-and-transactions.html
В основном мы реализуем ConnectionProvider
, который использует метод org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(ds)
для поиска и возврата соединения БД, которое содержит транзакцию, созданную с помощью Spring.
Создайте TransactionManager
bean для вашего DataSource
, как показано ниже:
<bean
id="dataSource"
class="org.apache.tomcat.jdbc.pool.DataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="mysql://locahost:3306/db_name"
p:username="root"
p:password="root"
p:initialSize="2"
p:maxActive="10"
p:maxIdle="5"
p:minIdle="2"
p:testOnBorrow="true"
p:validationQuery="/* ping */ SELECT 1"
/>
<!-- Configure the PlatformTransactionManager bean -->
<bean
id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<!-- Scan for the Transactional annotation -->
<tx:annotation-driven/>
Теперь вы можете аннотировать все классы или методы, которые используют jOOQ DSLContext
с
@Transactional(rollbackFor = Exception.class)
И при создании объекта DSLContext
jOOQ будет использовать транзакцию, созданную Spring.