我们使用removeAbandoned=true
配置tomcat-jdbc连接池。如果放弃连接,该选项确实有效,但连接只是关闭。使用Oracle,这意味着提交了当前事务(see this question)。这不好,因为不应该提交未完成的交易。
如何配置池,以便在放弃连接时首先回滚当前事务,然后关闭连接?
我尝试rollbackOnReturn=true
但是池似乎没有将它用于废弃的连接。
修改:我们使用defaultAutoCommit=false
编辑:这种情况的一个案例是集成测试的调试;由于这样的提交,我们的事务表被截断了
答案 0 :(得分:1)
根据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:
(boolean)标记,如果它们超过,则删除已放弃的连接 removeAbandonedTimeout。如果设置为true,则考虑连接 被遗弃并有资格被移除如果它已被使用超过 removeAbandonedTimeout 将此设置为true可以恢复db 来自无法关闭连接的应用程序的连接。看到 logAbandoned默认值为false。
我建议不要设置removeAbandoned
,以便Oracle在服务器端超时后关闭连接,而不是关闭它。在这种情况下,Oracle可能不会提交事务,但您需要对此进行测试。
或者,您是否可以增加removeAbandonedTimeout
设置,以便您的程序可以完成,并且不会放弃任何连接?
您遇到的另一个问题是您的应用程序已与Oracle绑定,因为您依赖于规范中存在漏洞的驱动程序实现。如果可以,可以针对规范进行编程,以便您可以自由地将应用程序迁移到其他数据库,尽管我知道这在实践中很难实现。
一个完全不同的解决方案是使用一个开源连接池,并使用AOP拦截器扩展它,该拦截器可以拦截对close
的调用,并在事务已经提交时计算出来,如果没有,则调用{关于连接的{1}}。这虽然是一个非常复杂的解决方案......: - )
答案 1 :(得分:0)
好的......我想如果你不能排除被遗弃的连接,你只有3个选项:
对于选项1,您可以编辑Tomcat源代码中的方法,通过HotSwap或Javassist替换它或者将它们全部禁用并编写自己的方法,循环所有连接并检测哪些是放弃并关闭它们
对于选项2,您可以为Connection-Interface编写自己的Wrapper,它将使用rollback + close替换close()调用并配置TomCat以将Connection包装到Wrapper-Class中,或者您可以使用{{ 3}}或HotSwap在运行时替换connection-Class中的Close-Method。
对于选项3,您将完全禁用已废弃连接的整体处理,并将数据库配置为在特定超时后终止空闲连接。但是,当它们长时间不使用时,这也会导致连接池中的连接断断续续... ...
答案 2 :(得分:0)
你可以注册一个JDBCInterceptor来做这个修改,所以你可以在它关闭之前回滚 - 看看这里:http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors。放弃将调用释放,这将调用断开连接,因此拦截器将被通知此事。 例如,您可以这样做:
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);
}
}