Detecting an open transaction not yet committed to a JDBC connection

How to determine if a transaction is still open, still pending a response to COMMIT

or ROLLBACK

from a JDBC Connection ?

I am getting Connection objects via connection pool. So I want to check the status of the connection before using it.

Using Postgres 9.x and Java 8.

+3


source to share


5 answers


I don't know how to determine the current status of a transaction Connection

using only standard JDBC API methods.

However, for PostgreSQL in particular, there is AbstractJdbc2Connection.getTransactionState()

one that you can compare to a constant ProtocolConnection.TRANSACTION_IDLE

. PostgreSQL JDBC4 Connection

extends this class, so you can use Connection

to access this property.



This constant is one of three values ​​defined in the source code pgjdbc

:

/**
 * 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;

      

+4


source


As I understand it, you are using plain JDBC and that is why you have this problem. Since you covered Tomcat JDBC connection pooling, you can use JDBCInterceptor.invoke()

where you can keep track of what happens to each Connection

. More details here .



+3


source


The answer from heenenee is correct.

Sample code

This answer hosts the source code for the helper class. This source code is based on ideas if this is the accepted 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;
    }

}

      

Usage example:

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.
}

      

Include JDBC driver in project but omit from assembly

The problem with this code is that at compile time we have to refer to classes specific to a particular JDBC driver , not generic JDBC interfaces .

You might be thinking, "Okay, just add the JDBC driver JAR file to the project." But no, in a Servlet web application environment, we shouldn't include the JDBC driver in our assembly (our WAR file / folder ). In a web application, technical problems mean that we have to put our JDBC driver in a Servlet container. For me this means Apache Tomcat where we put the JDBC driver jar file in its own /lib

Tomcats folder and not in our WAR file / WAR folder for web applications.

So how do I include the JDBC JAR driver in our project at compile time, excluding our WAR file from the build? See this question, Include the library at programming and compile time, but exclude from assembly in a NetBeans Maven project . The solution in Maven is a tag scope

with a value provided

.

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

      

+2


source


You can get the txId from postgres select txid_current()

and write it to the log. This number is different for different transactions.

0


source


I managed to do something with Statement.getUpdateCount (). The idea was that after executing each statement, I check to see if updateCount> 0. If it is true and that autocommit is disabled, it means that the connection of that statement will require a commit or rollback before closing.

By wrapping Datasource, Connection, Statement, PreparedStatement, CallableStatement, you can do this check on every call to execute (), executeUpdate (), executeBatch (), save the stack trace and flag in the connection wrapper. In a close () relationship, you can then show the execution of the last statement on the stack, then a rollback and an exception.

However, I'm not sure about the overhead of getUpdateCount () and if it doesn't mess up the results. But integration test cases go a long way.

We could check to see if getUpdateCount ()> -1, but it will break ode, which may already avoid committing if nothing has been updated.

0


source







All Articles