将 JOOQ 查询与 JDBC 事务混合

Posted

技术标签:

【中文标题】将 JOOQ 查询与 JDBC 事务混合【英文标题】:Mix JOOQ query with JDBC transaction 【发布时间】:2021-09-16 08:29:23 【问题描述】:

我有一个用例,我想将 jdbc 事务与 jooq 上下文混合。

JDBC 代码如下所示:

  public void inTransaction(InTransaction lambda) 
    DataSource ds = dataSource.get();
    try (Connection connection = ds.getConnection()) 
      try 
        logger.info("set autocommit to false");
        connection.setAutoCommit(false);
        try (Statement statement = connection.createStatement()) 
          lambda.execute(statement);
          logger.info("commiting transaction");
          connection.commit();
        
       catch (RuntimeException e) 
        logger.info("rolling back transaction");
        connection.rollback();
        throw e;
       finally 
        logger.info("set autocommit to true");
        connection.setAutoCommit(true);
      
     catch (SQLException e) 
      throw new TilerException(e);
    
  

  @FunctionalInterface
  public interface InTransaction 
    void execute(Statement statement) throws SQLException;
  

我希望lambda 参数能够与 jdbc 和 jooq 一起使用。

对于 jdbc,使用语句非常简单。例如这样的tutorail:

 inTransaction(stmt -> 
   String SQL = "INSERT INTO Employees  " +
                "VALUES (106, 20, 'Rita', 'Tez')";
   stmt.executeUpdate(SQL);
   String SQL = "INSERTED IN Employees  " +
                "VALUES (107, 22, 'Sita', 'Singh')";
   stmt.executeUpdate(SQL);
 );

为了对同一事务执行 jooq 查询,我必须获取上下文。我找到了一个 api 从数据源/连接中获取 DSLContext。 我不清楚的是是否/如何从声明中创建 jooq DSLContext

【问题讨论】:

我不明白你的问题。使用 DSL.using 创建 DslContext 时,您必须将连接传递给配置。并且提交发生在连接上。 我想传递一个语句而不是连接。我已经在使用该语句使用 jdbc 运行其他查询/更新。 很抱歉,您的代码没有意义,因为您只能在每个事务的语句上执行。这将与自动提交相同 除此之外还有一个未解决的问题:github.com/jOOQ/jOOQ/issues/3715 我添加了一个如何使用该方法的示例。同一事务的多个查询中使用相同的语句。 【参考方案1】:

您描述的问题的解决方案

您可以使用 jOOQ 的事务 API 完成所有这些操作:

// Create this ad-hoc, or inject it, or whatever
DSLContext ctx = DSL.using(dataSource, dialect);

然后:

public void inJDBCTransaction(InJDBCTransaction lambda) 
    ctx.transaction(config -> 
        config.dsl().connection(connection -> 
            try (Statement statement = connection.createStatement()) 
                lambda.execute(statement);
            
        );
    );


public void inJOOQTransaction(InJOOQTransaction lambda) 
    ctx.transaction(config -> lambda.execute(config.dsl()));


@FunctionalInterface
public interface InJDBCTransaction 
    void execute(Statement statement) throws SQLException;


@FunctionalInterface
public interface InJOOQTransaction 
    void execute(DSLContext ctx);

你的最终代码:

inJDBCTransaction(stmt -> 
    String SQL = "INSERT INTO Employees  " +
                 "VALUES (106, 20, 'Rita', 'Tez')";
    stmt.executeUpdate(SQL);
    String SQL = "INSERTED IN Employees  " +
                 "VALUES (107, 22, 'Sita', 'Singh')";
    stmt.executeUpdate(SQL);
);

inJOOQTransaction(ctx -> 
    ctx.insertInto(EMPLOYEES).values(106, 20, "Rita", "Tez").execute();
    ctx.insertInto(EMPLOYEES).values(107, 22, "Sita", "Singh").execute();
);

我不太相信这种抽象对 jOOQ 和 JDBC 的需求。 jOOQ 从不向您隐藏 JDBC。使用DSLContext.connection() 方法时,您始终可以访问如上所示的JDBC API。所以,如上图:

jOOQ 事务 API 完全符合您的计划。在事务上下文中包装 lambda,如果成功则提交,如果失败则回滚(您的版本的回滚不起作用,因为它捕获了错误的异常)。 如果需要“JDBC 逃生舱”,jOOQ 可以提供

旁注

在许多 RDBMS 中,您不希望在静态 JDBC Statement 上运行查询。你会想改用PreparedStatement,因为:

您将从执行计划缓存中获益(缓存争用更少) 您将避免语法错误(以防您的实际查询是动态的) 您将避免 SQL 注入问题

【讨论】:

【参考方案2】:

如果你想从 jOOQ 中获取查询字符串,你可以调用

String sqlString = query.getSQL()

然后在你的语句中使用这个字符串:

stmt.executeUpdate(sqlString);

【讨论】:

谢谢,这是一个很好的更新解决方案。就我而言,我也有查询(jooq 解析结果并创建对象),但我不知道如何使用它。 我会反其道而行之。对所有事情都使用 jOOQ。您还可以将 SQL 字符串传递给 jOOQ 以执行。

以上是关于将 JOOQ 查询与 JDBC 事务混合的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 和 jOOQ 共享事务

JOOQ:单个语句是隐式事务性的,还是我仍然必须将其包装在事务性块中?

Java JOOQ 与 SQL Server:缓存查询

使用 JOOQ 查询 mysql 中的 json 字段

记录真实的 JDBC 连接并生成 Jooq 模拟文件

星环 KunDB 2.2 发布,为高并发事务与查询混合的业务系统提供一个新选择