3

我们使用removeAbandoned=true. 如果连接被放弃,该选项确实有效,但连接只是关闭。对于 Oracle,这意味着当前事务已提交(请参阅此问题)。这不好,因为不应该提交未完成的事务。

如何配置一个池,以便如果放弃连接,则首先回滚当前事务,然后关闭连接?

我试过rollbackOnReturn=true了,但池似乎没有将它用于废弃的连接。

编辑:我们使用defaultAutoCommit=false

编辑:发生的一种情况是集成测试的调试;由于此类提交,我们的事务表被截断

4

3 回答 3

2

根据http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#close()

“强烈建议应用程序在调用 close 方法之前显式提交或回滚活动事务。如果调用 close 方法并且存在活动事务,则结果是实现定义的。

这个测试,使用 Mysql 而不是 Oracle 证实了这个事实:

import static org.junit.Assert.assertEquals;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.junit.Test;


public class DBTest {

    public Connection openConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
        c.setAutoCommit(false);
        return c;
    }

    @Test
    public void testSO25886466() throws SQLException, ClassNotFoundException {

        {
            Connection c = openConnection();
            PreparedStatement delete = c.prepareStatement("delete from temp");
            delete.executeUpdate();
            c.commit();
            c.close();
        }

        {
            Connection c = openConnection();
            PreparedStatement insert = c.prepareStatement("insert into temp values ('a', 'b')");
            insert.execute();
            //c.commit(); as the op says, DONT commit!!
            c.close(); //WITHOUT having closed the statement or committing the transaction!!
        }

        {
            Connection c = openConnection();
            PreparedStatement select = c.prepareStatement("select count(*) from temp");
            select.execute();
            ResultSet rs = select.getResultSet();
            while(rs.next()){
                assertEquals(0/*i'd expect zero here!*/, rs.getInt(1));
            }
            rs.close();
            select.close();
            c.close();
        }
    }
}

根据http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html

(布尔值)标志以在超过 removeAbandonedTimeout 时删除放弃的连接。如果设置为 true,则如果连接的使用时间超过 removeAbandonedTimeout,则该连接被视为已放弃并有资格删除。将此设置为 true 可以从无法关闭连接的应用程序中恢复数据库连接。另请参阅 logAbandoned 默认值为 false。

我建议不要设置removeAbandoned,以便 Oracle 在服务器端超时后关闭连接,而不是 Tomcat 关闭它。在这种情况下,Oracle 可能不会提交事务,但您需要对此进行测试。

或者,您是否可以增加removeAbandonedTimeout设置,以便您的程序可以完成,并且不会放弃任何连接?

您遇到的另一个问题是您的应用程序已与 Oracle 绑定,因为您依赖于规范中存在漏洞的驱动程序实现。如果可以,请根据规范进行编程,以便您可以自由地将应用程序迁移到不同的数据库,尽管我知道这在实践中很难。

一个完全不同的解决方案是采用开源连接池,并使用 AOP 拦截器对其进行扩展,该拦截器可以拦截对事务的调用close并确定事务是否已提交,如果没有,则调用rollback连接。这是一个相当复杂的解决方案...... :-)

于 2014-09-25T20:16:09.350 回答
0

好的....我认为如果您不能排除放弃的连接,您只有 3 个选项:

  1. 将关闭废弃连接的Tomcats ConnectionPool的方法更改为回滚+关闭
  2. 更改连接的关闭方法以执行回滚+关闭
  3. 超时后让数据库回滚+关闭连接(禁用Tomcats处理程序)

对于选项 1,您可以在 Tomcat 源代码中编辑方法,通过HotSwapJavassist替换它,或者一起禁用它并编写您自己的方法,该方法循环所有连接并检测哪些被放弃并关闭它们

对于选项 2,您可以为 Connection-Interface 编写自己的 Wrapper,它将用 rollback+close 替换 close() 调用并配置 TomCat 以将 Connection 包装到 Wrapper-Class 中,或者您可以使用HotSwapJavassist来替换运行时连接类中的关闭方法。

对于选项 3,您一起禁用对废弃连接的整体处理,并配置您的数据库以在特定超时后终止空闲连接。但这会带来不利的一面,即当连接池中的连接长时间不使用时,它们也会定期关闭它们......

于 2014-09-26T08:34:24.113 回答
0

你可以只注册一个 JDBCInterceptor 来做这个修改,所以你可以在它关闭之前回滚 - 看这里:http: //tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors。Abandon 将调用 release 将调用断开连接,因此拦截器将被通知此。例如,您可以这样做:

package test;
import java.sql.SQLException;
import oracle.jdbc.OracleConnection;

import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
import org.apache.tomcat.jdbc.pool.PooledConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class RollbackInterceptor extends JdbcInterceptor {

  /**
   * Logger.
   */
private static final Logger LOG = LoggerFactory.getLogger(RollbackInterceptor.class);

/**
* {@inheritDoc}
*/
@Override
public void reset(ConnectionPool parent, PooledConnection con) {
  return;
}

/**
 * {@inheritDoc}
 */
@Override
public void disconnected(ConnectionPool parent, PooledConnection con, boolean finalizing) {
  // if its oracle make sure we rollback here before disconnect just in case a running TX is open
  try {
    if (con.getConnection().isWrapperFor(OracleConnection.class)) {
      if (!con.getConnection().getAutoCommit()) {
        LOG.error("Connection {} with Auto-Commit false is going to be closed. Doing an explicit Rollback here!", con);
        try {
          con.getConnection().rollback();
        } catch (SQLException e) {
          LOG.error("Failed to rollback connection {} before closing it.", con, e);
        }
      }
    }
  } catch (SQLException e) {
    LOG.error("Failed to check auto commit of connection {}", con, e);
  }
  super.disconnected(parent, con, finalizing);
}

}
于 2015-05-12T11:17:33.637 回答