JOOQ & 交易

Posted

技术标签:

【中文标题】JOOQ & 交易【英文标题】:JOOQ & transactions 【发布时间】:2013-12-01 12:17:48 【问题描述】:

我一直在阅读有关 transactions & 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 中,添加了 transaction 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();
       
   );

这个想法是 lambda 表达式(或匿名类)形成事务代码,其中:

在正常完成时提交 发生异常时回滚

org.jooq.TransactionProvider SPI 可用于覆盖默认行为,即使用Savepoints 通过 JDBC 实现可嵌套事务。

Spring 示例

当前文档显示了使用 Spring 进行事务处理的示例:

http://www.jooq.org/doc/latest/manual/getting-started/tutorials/jooq-with-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 上提供了一个运行示例:

https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-example

Spring 和 Guice 示例

虽然我个人不推荐,但一些用户已经成功地用 Guice 替换了 Spring 的部分 DI 并使用 Guice 处理事务。这个用例在 GitHub 上还有一个经过集成测试的运行示例:

https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-guice-example

【讨论】:

感谢您的回答 - 您介意评论我提出的解决方案吗? (除了它是一个快速而丑陋的修复!)【参考方案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());
    

【讨论】:

我明白了。正如您所说,我认为您的解决方案并不是真正的“黑客”。正确获取连接生命周期和事务管理是一个不小的问题。也许,您可以在PostgresConnectionProvider 的终结器中添加一些日志记录代码,它会检测何时存在未返回的连接?我会更多地考虑这些事情。我有几个想法I explained in this thread on the user group 是的好主意。感谢您的链接:我看过一些讨论,但不是那个。【参考方案3】:

(我发现)将 Spring Transactions 与 jOOQ 一起使用的最简单方法在这里给出:http://blog.liftoffllc.in/2014/06/jooq-and-transactions.html

基本上我们实现了一个ConnectionProvider,它使用org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(ds)方法来查找并返回保存由Spring创建的事务的数据库连接。

为您的DataSource 创建一个TransactionManager bean,示例如下:

  <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 创建的事务。

【讨论】:

【参考方案4】:

虽然这是一个老问题,但请查看此链接以帮助配置 JOOQ 以使用 spring 提供的事务管理器。您的数据源和 DSLContext 必须了解 Transacation。

https://www.baeldung.com/jooq-with-spring

你可能需要改变

@Bean
public DefaultDSLContext dsl() 
    return new DefaultDSLContext(configuration());

@Bean
public DSLContext dsl() 
    return new DefaultDSLContext(configuration());

【讨论】:

以上是关于JOOQ & 交易的主要内容,如果未能解决你的问题,请参考以下文章

Jooq 不使用春季交易

使用 scala 在 Jooq 中进行交易和条件更新

多个存储库上的 JOOQ @Transactional

JOOQ 插入记录列表

jOOQ 是不是使用不同的连接来执行不同的查询?

券商平台应接受&鼓励社区交易的影响力,一起打造更公平的交易生态环境