检测尚未在 JDBC 连接中提交的打开事务
Detect open transaction not yet committed in JDBC connection
如何检测交易是否仍处于打开状态,仍在 COMMIT
或 ROLLBACK
上 JDBC Connection 上挂起?
我正在通过连接池获取我的连接对象。所以我想在使用之前检查连接状态。
使用 Postgres 9.x 和 Java 8.
我不知道有什么方法可以仅使用标准 JDBC API 方法来检测 Connection
上的当前交易状态。
但是,对于 PostgreSQL 具体来说,有 AbstractJdbc2Connection.getTransactionState()
, which you can compare against the constant ProtocolConnection.TRANSACTION_IDLE
。 PostgreSQL 的 JDBC4 Connection
扩展了这个 class 所以你应该能够转换你的 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;
据我了解,您使用的是纯 JDBC,这就是您遇到此问题的原因。因为您提到了 Tomcat 的 JDBC 连接池,所以您可以使用 JDBCInterceptor.invoke()
, where you could track what happens to each Connection
. More details here。
您可以从 postgres select txid_current()
检索 txId 并将其写入日志。这个数字对于不同的交易是不同的。
by heenenee 是正确的。
示例代码
此答案发布了帮助程序 class 的源代码。此源代码基于接受答案的想法。
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 Whosebug:
// 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 驱动程序但在构建中省略
此代码的挑战在于,在编译时我们必须解决 class 特定于 a specific JDBC driver rather than generalized JDBC 接口的问题。
您可能会想,“好吧,只需将 JDBC 驱动程序 jar 文件添加到项目中即可”。但是,不,在网络应用程序中 Servlet environment we must not include the JDBC driver in our build (our WAR file/folder)。在 Web 应用程序中,技术问题意味着我们应该将 JDBC 驱动程序存放在 Servlet 容器中。对我来说,这意味着 Apache Tomcat 我们将 JDBC 驱动程序 jar 文件放入 Tomcat 自己的 /lib
文件夹中,而不是我们的网络应用程序 WAR file/folder.
那么如何在编译时将 JDBC 驱动程序 jar 包含在我们的项目中,同时从我们的 WAR 文件的构建中排除?请参阅此问题,。 Maven中的解决方案是scope
标签,值为provided
.
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
<scope>provided</scope>
</dependency>
我设法用 Statement.getUpdateCount() 做了一些事情。
我的想法是,在每个语句执行后,我验证 updateCount > 0 是否。
如果它是 true 并且自动提交被禁用,则意味着该语句的连接在关闭之前需要提交或回滚。
通过包装 Datasource、Connection、Statement、PreparedStatement、CallableStatement,可以在每次调用 execute()、executeUpdate()、executeBatch() 时实现此验证,将堆栈跟踪和标志存储在 Connection 包装器中.
在 connection close() 中,您可以使用 stack 显示最后一条语句执行,然后回滚并抛出异常。
但是我不确定 getUpdateCount() 的开销,以及它是否不会影响结果。
但是集成测试用例正在发挥作用。
我们可以检查 getUpdateCount() 是否 >-1,但如果没有任何更新,它可能会破坏可能已经避免提交的颂歌。
如何检测交易是否仍处于打开状态,仍在 COMMIT
或 ROLLBACK
上 JDBC Connection 上挂起?
我正在通过连接池获取我的连接对象。所以我想在使用之前检查连接状态。
使用 Postgres 9.x 和 Java 8.
我不知道有什么方法可以仅使用标准 JDBC API 方法来检测 Connection
上的当前交易状态。
但是,对于 PostgreSQL 具体来说,有 AbstractJdbc2Connection.getTransactionState()
, which you can compare against the constant ProtocolConnection.TRANSACTION_IDLE
。 PostgreSQL 的 JDBC4 Connection
扩展了这个 class 所以你应该能够转换你的 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;
据我了解,您使用的是纯 JDBC,这就是您遇到此问题的原因。因为您提到了 Tomcat 的 JDBC 连接池,所以您可以使用 JDBCInterceptor.invoke()
, where you could track what happens to each Connection
. More details here。
您可以从 postgres select txid_current()
检索 txId 并将其写入日志。这个数字对于不同的交易是不同的。
示例代码
此答案发布了帮助程序 class 的源代码。此源代码基于接受答案的想法。
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 Whosebug:
// 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 驱动程序但在构建中省略
此代码的挑战在于,在编译时我们必须解决 class 特定于 a specific JDBC driver rather than generalized JDBC 接口的问题。
您可能会想,“好吧,只需将 JDBC 驱动程序 jar 文件添加到项目中即可”。但是,不,在网络应用程序中 Servlet environment we must not include the JDBC driver in our build (our WAR file/folder)。在 Web 应用程序中,技术问题意味着我们应该将 JDBC 驱动程序存放在 Servlet 容器中。对我来说,这意味着 Apache Tomcat 我们将 JDBC 驱动程序 jar 文件放入 Tomcat 自己的 /lib
文件夹中,而不是我们的网络应用程序 WAR file/folder.
那么如何在编译时将 JDBC 驱动程序 jar 包含在我们的项目中,同时从我们的 WAR 文件的构建中排除?请参阅此问题,scope
标签,值为provided
.
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
<scope>provided</scope>
</dependency>
我设法用 Statement.getUpdateCount() 做了一些事情。 我的想法是,在每个语句执行后,我验证 updateCount > 0 是否。 如果它是 true 并且自动提交被禁用,则意味着该语句的连接在关闭之前需要提交或回滚。
通过包装 Datasource、Connection、Statement、PreparedStatement、CallableStatement,可以在每次调用 execute()、executeUpdate()、executeBatch() 时实现此验证,将堆栈跟踪和标志存储在 Connection 包装器中. 在 connection close() 中,您可以使用 stack 显示最后一条语句执行,然后回滚并抛出异常。
但是我不确定 getUpdateCount() 的开销,以及它是否不会影响结果。 但是集成测试用例正在发挥作用。
我们可以检查 getUpdateCount() 是否 >-1,但如果没有任何更新,它可能会破坏可能已经避免提交的颂歌。