Подтвердить что ты не робот

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, потому что есть только одно соединение - но с пулом я не уверен, как это сделать.

4b9b3361

Ответ 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 для обработки транзакций:

Этот пример, по существу, сводится к использованию 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:

A Spring и пример Guice

Хотя я лично не рекомендовал бы этого, некоторые пользователи успели заменить часть 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.