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, потому что есть только одно соединение - но с пулом я не знаю, как это сделать.
3 ответа
jOOQ 3.4 API транзакций
В версии jOOQ 3.4 API-интерфейс транзакций был добавлен для абстрагирования через менеджеры транзакций JDBC, Spring или JTA. Этот API может использоваться с Java 8 как таковой:
DSL.using(configuration)
.transaction(ctx -> {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
});
Или с синтаксисом до 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();
}
});
Идея состоит в том, что лямбда-выражение (или анонимный класс) образуют транзакционный код, который:
- Фиксируется при нормальном завершении
- Откатывается при исключении
org.jooq.TransactionProvider
SPI может использоваться для переопределения поведения по умолчанию, которое реализует вложенные транзакции через JDBC, используя Savepoints
,
Весенний пример
В текущей документации показан пример использования 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's ConnectionProvider -->
<bean class="org.jooq.impl.DataSourceConnectionProvider"
name="connectionProvider">
<constructor-arg ref="transactionAwareDataSource" />
</bean>
Рабочий пример доступен на GitHub здесь:
Пример Spring и Guice
Хотя я лично не рекомендовал бы это, некоторые пользователи имели успех, заменив часть DI Spring на Guice и обрабатывая транзакции с Guice. Для этого варианта использования также есть пример запуска, протестированный на интеграцию на GitHub:
Это, вероятно, не лучший способ, но, похоже, работает. Предостережение заключается в том, что это не 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());
}
}
Самый простой способ (я нашел) использовать Spring Транзакции с jOOQ приведен здесь: http://blog.liftoffllc.in/2014/06/jooq-and-transactions.html
В основном мы реализуем ConnectionProvider
который использует org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(ds)
метод, чтобы найти и вернуть соединение с БД, содержащее транзакцию, созданную Spring.
Создать TransactionManager
боб для вашего 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.
Хотя это старый вопрос, пожалуйста, просмотрите эту ссылку, чтобы помочь настроить JOOQ для использования предоставленного Spring менеджера транзакций. Ваш источник данных и DSLContext должны знать о транзакциях.
https://www.baeldung.com/jooq-with-spring
Возможно, вам придется изменить
@Bean
public DefaultDSLContext dsl() {
return new DefaultDSLContext(configuration());
}
в
@Bean
public DSLContext dsl() {
return new DefaultDSLContext(configuration());
}