Detect open transaction not yet committed in JDBC connection

时间:2015-07-31 19:58:08

标签: java jdbc transactions connection-pooling

How do I detect if a transaction remains open, still pending on a COMMIT or ROLLBACK on a JDBC Connection?

I'm getting my Connection objects via a connection pool. So I want to check the state of the connection before using it.

Using Postgres 9.x and Java 8.

5 个答案:

答案 0 :(得分:4)

我不知道有任何方法只使用标准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;

答案 1 :(得分:3)

据我所知,您使用普通的JDBC,这就是您遇到此问题的原因。因为你讲过Tomcat的JDBC连接池,你可以使用JDBCInterceptor.invoke(),在那里你可以跟踪每个Connection发生的事情。更多详情here

答案 2 :(得分:2)

accepted Answer heenenee是正确的。

示例代码

本答案发布了助手类的源代码。如果接受了答案,则此源代码基于这些想法。

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>

答案 3 :(得分:0)

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

答案 4 :(得分:0)

我设法用Statement.getUpdateCount()做了一些事情。 我们的想法是,在每次执行语句后,我都会验证updateCount&gt; 0。 如果它是真的并且自动提交被禁用,则意味着该语句的连接在关闭之前需要提交或回滚。

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

但是我不确定getUpdateCount()的开销,如果它没有弄乱结果。 但集成测试用例正在发挥作用。

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