【问题标题】:Detect open transaction not yet committed in JDBC connection检测 JDBC 连接中尚未提交的打开事务
【发布时间】:2015-10-23 14:34:59
【问题描述】:

我如何检测事务是否保持打开状态,是否仍在 JDBC Connection 上的 COMMITROLLBACK 上挂起?

我正在通过连接池获取我的 Connection 对象。所以我想在使用前检查连接的状态。

使用 Postgres 9.x 和 Java 8。

【问题讨论】:

  • 一个正确实现的连接池应该rollback(),然后再将连接返回到池中。
  • 我尝试阅读Tomcat JDBC Connection Pool 实现的源代码,但无法确定这种行为。代理、拦截器等的使用使源代码难以遵循。
  • @PaulWhite 我想Answer by heenenee 是最好的;它应该可以工作(我还没有测试过)并且足够优雅。它唯一的缺点是 Postgres 特定的,但显然没有通用的方法。 Answer by sibnick 信息丰富且聪明(如果我正确推断出它的应用程序,根据我的评论),但不优雅,因为我不喜欢仅仅为了检测开放交易而不断增加交易计数器的想法。我不明白 Andrei I 剩下的答案。

标签: java jdbc transactions connection-pooling


【解决方案1】:

我不知道有任何方法可以仅使用标准 JDBC API 方法检测 Connection 上的当前事务状态。

但是,特别是对于 PostgreSQL,有 AbstractJdbc2Connection.getTransactionState(),您可以将其与常量 ProtocolConnection.TRANSACTION_IDLE 进行比较。 PostgreSQL 的 JDBC4 Connection 扩展了这个类,所以你应该能够转换你的 Connection 来访问这个属性。

该常量是pgjdbc 驱动程序source code 中定义的三个值之一:

/**
 * Constant returned by {@link #getTransactionState} indicating that no
 * transaction is currently open.
 */
static final int TRANSACTION_IDLE = 0;

/**
 * Constant returned by {@link #getTransactionState} indicating that a
 * transaction is currently open.
 */
static final int TRANSACTION_OPEN = 1;

/**
 * Constant returned by {@link #getTransactionState} indicating that a
 * transaction is currently open, but it has seen errors and will
 * refuse subsequent queries until a ROLLBACK.
 */
static final int TRANSACTION_FAILED = 2;

【讨论】:

    【解决方案2】:

    据我了解,您使用的是普通 JDBC,这就是您遇到此问题的原因。因为您提到了 Tomcat 的 JDBC 连接池,所以您可以使用 JDBCInterceptor.invoke(),您可以在其中跟踪每个 Connection 发生的情况。更多详情here.

    【讨论】:

      【解决方案3】:

      heeneneeaccepted Answer 是正确的。

      示例代码

      此答案发布了帮助程序类的源代码。此源代码基于已接受答案的想法。

      package com.powerwrangler.util;
      
      import java.sql.Connection;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.sql.Statement;
      import java.util.UUID;
      import javax.naming.Context;
      import javax.naming.InitialContext;
      import javax.naming.NamingException;
      import javax.sql.DataSource;
      import org.slf4j.LoggerFactory;
      
      /**
       *
       * Help with database chores.
       *
       * © 2015 Basil Bourque 
       * This source code available under terms of the ISC License. http://opensource.org/licenses/ISC
       *
       * @author Basil Bourque.
       *
       */
      public class DatabaseHelper
      {
      
          static final org.slf4j.Logger logger = LoggerFactory.getLogger( DatabaseHelper.class );
      
          public enum TransactionState
          {
      
              IDLE,
              OPEN,
              FAILED;
      
          }
      
          /**
           * If using the Postgres database, and the official "org.postgresql" JDBC driver, get the current state of the
           * current transaction held by a Connection. Translate that state to a convenient Enum value.
           *
           * @param connArg
           *
           * @return DatabaseHelper.TransactionState
           */
          public DatabaseHelper.TransactionState transactionStateOfConnection ( Connection connArg ) {
              // This code is specific to Postgres.
              // For more info, see this page on StackOverflow:  https://stackoverflow.com/q/31754214/642706
      
              // Verify arguments.
              if ( connArg == null ) {
                  logger.error( "Received null argument for Connection object. Message # 6b814e3c-80e3-4145-9648-390b5315243e." );
              }
      
              DatabaseHelper.TransactionState stateEnum = null;  // Return-value.
      
              Connection conn = connArg;  // Transfer argument to local variable.
      
              // See if this is a pooled connection.
              // If pooled, we need to extract the real connection wrapped inside.
              // Class doc: http://docs.oracle.com/javase/8/docs/api/javax/sql/PooledConnection.html
              // I learned of this via the "Getting the actual JDBC connection" section of the "Tomcat JDBC Connection Pool" project.
              // Tomcat doc: https://tomcat.apache.org/tomcat-8.0-doc/jdbc-pool.html#Getting_the_actual_JDBC_connection
              if ( conn instanceof javax.sql.PooledConnection ) {
                  javax.sql.PooledConnection pooledConnection = ( javax.sql.PooledConnection ) conn;
                  try { // Can throw java.sql.SQLException. So using a Try-Catch.
                      // Conceptually we are extracting a wrapped Connection from with in a PooledConnection. Reality is more complicated.
                      // From class doc: Creates and returns a Connection object that is a handle for the physical connection that this PooledConnection object represents.
                      conn = pooledConnection.getConnection();
                  } catch ( SQLException ex ) {
                      // We could just as well throw this SQLException up the call chain. But I chose to swallow it here. --Basil Bourque
                      logger.error( "Failed to extract the real Connection from its wrappings in a PooledConnection. Message # ea59e3a3-e128-4386-949e-a70d90e1c19e." );
                      return null; // Bail-out.
                  }
              }
      
              // First verify safe to cast.
              if ( conn instanceof org.postgresql.jdbc2.AbstractJdbc2Connection ) {
                  // Cast from a generalized JDBC Connection to one specific to our expected Postgres JDBC driver.
                  org.postgresql.jdbc2.AbstractJdbc2Connection aj2c = ( org.postgresql.jdbc2.AbstractJdbc2Connection ) conn; // Cast to our Postgres-specific Connection.
      
                  // This `getTransactionState` method is specific to the Postgres JDBC driver, not general JDBC.
                  int txnState = aj2c.getTransactionState();
                  // We compare that state’s `int` value by comparing to constants defined in this source code:
                  // https://github.com/pgjdbc/pgjdbc/blob/master/org/postgresql/core/ProtocolConnection.java#L27
                  switch ( txnState ) {
                      case org.postgresql.core.ProtocolConnection.TRANSACTION_IDLE:
                          stateEnum = DatabaseHelper.TransactionState.IDLE;
                          break;
      
                      case org.postgresql.core.ProtocolConnection.TRANSACTION_OPEN:
                          stateEnum = DatabaseHelper.TransactionState.OPEN;
                          break;
      
                      case org.postgresql.core.ProtocolConnection.TRANSACTION_FAILED:
                          stateEnum = DatabaseHelper.TransactionState.FAILED;
                          break;
      
                      default:
                          // No code needed.
                          // Go with return value having defaulted to null.
                          break;
                  }
              } else {
                  logger.error( "The 'transactionStateOfConnection' method was passed Connection that was not an instance of org.postgresql.jdbc2.AbstractJdbc2Connection. Perhaps some unexpected JDBC driver is in use. Message # 354076b1-ba44-49c7-b987-d30d76367d7c." );
                  return null;
              }
              return stateEnum;
          }
      
          public Boolean isTransactionState_Idle ( Connection connArg ) {
              Boolean b = this.transactionStateOfConnection( connArg ).equals( DatabaseHelper.TransactionState.IDLE );
              return b;
          }
      
          public Boolean isTransactionState_Open ( Connection conn ) {
              Boolean b = this.transactionStateOfConnection( conn ).equals( DatabaseHelper.TransactionState.OPEN );
              return b;
          }
      
          public Boolean isTransactionState_Failed ( Connection conn ) {
              Boolean b = this.transactionStateOfConnection( conn ).equals( DatabaseHelper.TransactionState.FAILED );
              return b;
          }
      
      }
      

      示例用法:

      if ( new DatabaseHelper().isTransactionState_Failed( connArg ) ) {
          logger.error( "JDBC transaction state is Failed. Expected to be Open. Cannot process source row UUID: {}. Message # 9e633f31-9b5a-47bb-bbf8-96b1d77de561." , uuidOfSourceRowArg );
          return null; // Bail-out.
      }
      

      在项目中包含 JDBC 驱动程序但从构建中省略

      此代码的挑战在于,在编译时我们必须处理特定于a specific JDBC driver 的类,而不是generalized JDBC 接口。

      您可能会想,“好吧,只需将 JDBC 驱动程序 jar 文件添加到项目中即可”。但是,不,在 web 应用程序 Servlet 环境中,我们必须在我们的构建中包含 JDBC 驱动程序(我们的 WAR file/folder)。在 Web 应用程序中,技术问题意味着我们应该将 JDBC 驱动程序存放在 Servlet 容器中。对我来说,这意味着我们将 JDBC 驱动程序 jar 文件放入 Tomcat 自己的 /lib 文件夹而不是我们的 Web 应用程序的 WAR 文件/文件夹中的 Apache Tomcat。

      那么,如何在编译时将 JDBC 驱动程序 jar 包含在我们的项目中,同时从 WAR 文件的构建中排除呢?请参阅此问题,Include a library while programming & compiling, but exclude from build, in NetBeans Maven-based project。 Maven中的解决方案是scope标签,其值为provided

      <dependency>
          <groupId>org.postgresql</groupId>
          <artifactId>postgresql</artifactId>
          <version>9.4-1201-jdbc41</version>
          <scope>provided</scope>
      </dependency>
      

      【讨论】:

        【解决方案4】:

        您可以从 postgres select txid_current() 检索 txId 并将其写入日志。这个数字对于不同的交易是不同的。

        【讨论】:

        • 我不明白这是如何回答这个问题的。该命令在自动提交模式和事务模式下都返回一个数字。由于自动提交在技术上确实启动了事务,并在执行单个语句后结束事务,所以对select txid_current() 的每次调用都会返回一个递增的数字。如果在正在进行的事务中,对select txid_current() 的每次调用都会返回相同的数字。我想可以使用对该命令的双重调用来确定交易是否有效(通过返回相同或不同的数字),我希望有一个更优雅的解决方案。
        • 虽然不是我的问题的答案,但让我提一下,Postgres 带来了一个新功能,可以在网络连接丢失或崩溃后了解最近事务的状态以进行恢复。请参阅第二象限的this article by Craig Ringer
        【解决方案5】:

        我设法用 Statement.getUpdateCount() 做一些事情。 这个想法是在每个语句执行后,我验证是否 updateCount > 0 。 如果为真并且自动提交被禁用,则意味着该语句的连接需要提交或回滚才能关闭。

        通过包装 Datasource、Connection、Statement、PreparedStatement、CallableStatement,可以在每次调用 execute()、executeUpdate()、executeBatch() 时实现此验证,并将堆栈跟踪和标志存储在 Connection 包装器中。 然后在连接 close() 中,您可以使用堆栈显示最后一条语句执行,然后回滚并抛出异常。

        但是我不确定 getUpdateCount() 的开销,以及它是否不会影响结果。 但是集成测试用例还很有效。

        我们可以检查 getUpdateCount() 是否 >-1,但如果没有更新,它会破坏可能已经避免提交的 ode。

        【讨论】:

          猜你喜欢
          • 2021-01-23
          • 1970-01-01
          • 1970-01-01
          • 2017-07-29
          • 2010-12-24
          • 1970-01-01
          • 1970-01-01
          • 2015-12-23
          • 1970-01-01
          相关资源
          最近更新 更多