怎样实现关闭connection时自动关闭Statement和ResultSet

时间:2022-09-02 11:50:36


转自:怎样实现关闭connection时自动关闭Statement和ResultSet


关闭数据连接时, 一定也要关闭Statement和ResultSet, 不然在并发量较大的时候可能导致内存泄漏. (如果是用tomcat自带的数据源实现, 则可以通过添加interceptor实现自动关闭statement.)


但是, 关闭Statement和ResultSet是乏味的工作.


例如下面的代码:

Java代码  怎样实现关闭connection时自动关闭Statement和ResultSet
  1. @Test  
  2. public void testConnection() throw Exception{  
  3.     Connection conn = null;  
  4.     PreparedStatement ps = null;  
  5.     ResultSet rs = null;  
  6.     try{  
  7.         conn = DsUtil.getConnection();  
  8.         ps = conn.prepareStatement("select now()");  
  9.         rs = ps.executeQuery();  
  10.         //do something  
  11.     }finally {  
  12.         //DsUtil是一个工具类, 就不贴代码了, 你懂的.  
  13.         DsUtil.close(rs);  
  14.         DsUtil.close(ps);  
  15.         DsUtil.close(conn);  
  16.     }  
  17. }  

上面的代码只有一个Statement和一个ResultSet, 关闭一下也不算太麻烦, 所以你可能觉得笔者没有必要写这篇文章.


但是, 如果有多个Statement和ResultSet呢?


考虑如下代码:

Java代码  怎样实现关闭connection时自动关闭Statement和ResultSet
  1. @Test  
  2. public void testConnection() throws Exception {  
  3.     Connection conn = null;  
  4.     PreparedStatement psFoo = null;  
  5.     ResultSet rsFoo = null;  
  6.     PreparedStatement psBar = null;  
  7.     ResultSet rsBar = null;  
  8.     try {  
  9.         conn = DsUtil.getConnection();  
  10.         psFoo = conn.prepareStatement("....");  
  11.         rsFoo = psFoo.executeQuery();  
  12.         psBar = conn.prepareStatement("....");  
  13.         rsBar = psBar.executeQuery();  
  14.         //do something  
  15.     } finally {  
  16.         DsUtil.close(rsFoo);  
  17.         DsUtil.close(psFoo);  
  18.         DsUtil.close(rsBar);  
  19.         DsUtil.close(psBar);  
  20.         DsUtil.close(conn);  
  21.     }  
  22. }  

上面的代码有两个statement和result, 关起来就不那么令人愉快了.


大多数程序员肯定是不喜欢关闭多个statement和result的. 我见到过偷懒的程序用这种写法来规避关闭多个statement的问题.

Java代码  怎样实现关闭connection时自动关闭Statement和ResultSet
  1. @Test  
  2. public void testConnectionNotGood() throws Exception {  
  3.     Connection conn = null;  
  4.     PreparedStatement psFoo = null;  
  5.     ResultSet rsFoo = null;  
  6.     try {  
  7.         conn = DsUtil.getConnection();  
  8.         psFoo = conn.prepareStatement("....");  
  9.         rsFoo = psFoo.executeQuery();  
  10.         //do something              
  11.         psFoo = conn.prepareStatement("....");  
  12.         rsFoo = psFoo.executeQuery();  
  13.         //do something  
  14.     } finally {  
  15.         DsUtil.close(rsFoo);  
  16.         DsUtil.close(psFoo);  
  17.         DsUtil.close(conn);  
  18.     }  
  19. }  

上面这断代码的不妥之处, 是Statement和ResultSet被重用了. 实际上创建了两个Statement和Result, 但最后只关闭了一个. 这样显然是不对的, 属于鸵鸟政策, 没解决实际问题.


那么, 有没有办法实现一个自定义的Connection, 使得程序员不需要手动关闭Statement和Result, 并支持Statement/PrepareStatement/CallableStatement呢?

 

==================分割线========================

 简单地说:

    我们希望connection持有statement的软引用, 而statement又持有resultset的软引用, 并分别重写connection和statement的close方法, 在关闭之前先关闭软引用中的对象. 

详细地说:
    1. 创建一个ResultSetStatementAwareConnection.

        该自定义Connection会记住所有的Statement/PreparedStatement/CallableStatement实例.
    2. 创建一个ResultSetAwareStatement, 记住所有Statement.
    3. 创建一个ResultSetAwarePreparedStatement, 记住所有的PreparedStatement.

    4. 创建一个ResultSetAwareCallableStatement, 记住所有的CallableStatement.

 

先说ResultSetStatementAwareConnection的实现.

Java代码  怎样实现关闭connection时自动关闭Statement和ResultSet
  1. class ResultSetStatementAwareConnection implements Connection {  
  2.     private Map> openStatements = new ConcurrentHashMap>(3);  
  3.     protected Connection underlyingConnection;  
  4.   
  5.     public ResultSetStatementAwareConnection(Connection conn) {  
  6.         this.underlyingConnection = conn;  
  7.     }  
  8.   
  9.     private void addToOpenStatement(Statement statement) {  
  10.         openStatements.put(DsUtil.getIdentityHexString(statement), new SoftReference(statement));  
  11.     }  
  12.   
  13.     public void close() throws SQLException {  
  14.         try {  
  15.             closeOpenStatements();  
  16.         } finally {  
  17.             closeUnderlyingConnection();  
  18.         }  
  19.     }  
  20.   
  21.     private void closeOpenStatements() {  
  22.         for (Map.Entry> entry : openStatements.entrySet()) {  
  23.             DsUtil.close(entry.getValue().get());  
  24.         }  
  25.         openStatements.clear();  
  26.         openStatements = null;  
  27.     }  
  28.   
  29.     protected void closeUnderlyingConnection() {  
  30.         DsUtil.close(underlyingConnection);  
  31.         underlyingConnection = null;  
  32.     }  
  33.   
  34.     @Override  
  35.     public Statement createStatement() throws SQLException {  
  36.         ResultSetAwareStatement statement = ResultSetAwareStatement.decorate(underlyingConnection.createStatement());  
  37.         statement.setConnection(this);  
  38.         addToOpenStatement(statement);  
  39.         return statement;  
  40.     }  
  41.   
  42.     @Override  
  43.     public PreparedStatement prepareStatement(String sql) throws SQLException {  
  44.         ResultSetAwarePreparedStatement statement = ResultSetAwarePreparedStatement.decorate(underlyingConnection.prepareStatement(sql));  
  45.         statement.setConnection(this);  
  46.         addToOpenStatement(statement);  
  47.         return statement;  
  48.     }  
  49.   
  50.     @Override  
  51.     public CallableStatement prepareCall(String sql) throws SQLException {  
  52.         ResultSetAwareCallableStatement statement = ResultSetAwareCallableStatement.decorate(underlyingConnection.prepareCall(sql));  
  53.         statement.setConnection(this);  
  54.         addToOpenStatement(statement);  
  55.         return statement;  
  56.     }  
  57.   
  58.     @Override  
  59.     public String getCatalog() throws SQLException {  
  60.         return underlyingConnection.getCatalog();  
  61.     }  
  62.   
  63.     //更多代码见附件  
  64. }  

通过openStatements持有所有statement的软引用, 并且close方法中会先调用closeOpenStatements把软引用持有的statement全部关闭, 然后再通过closeUnderlyingConnection去真正关闭连接.


到现在为止, 就只需要关闭数据库连接, 不需要显式关闭statement了.


类似地, statement也可以通过这种模式来关闭resultset. 以PreparedStatement为例.

Java代码  怎样实现关闭connection时自动关闭Statement和ResultSet
  1. class ResultSetAwarePreparedStatement implements PreparedStatement {  
  2.     private Map> openResultSets = new ConcurrentHashMap>(3);  
  3.     private ResultSetStatementAwareConnection connection;  
  4.     private PreparedStatement underlyingPreparedStatement;  
  5.   
  6.     static ResultSetAwarePreparedStatement decorate(PreparedStatement statement) {  
  7.         ResultSetAwarePreparedStatement instance = new ResultSetAwarePreparedStatement();  
  8.         instance.underlyingPreparedStatement = statement;  
  9.         return instance;  
  10.     }  
  11.   
  12.     private void addToOpenResultSet(ResultSet resultSet) {  
  13.         openResultSets.put(DsUtil.getIdentityHexString(resultSet), new SoftReference(resultSet));  
  14.     }  
  15.   
  16.     public void close() throws SQLException {  
  17.         try {  
  18.             closeOpenResultSets();  
  19.         } finally {  
  20.             closeUnderlyingStatement();  
  21.         }  
  22.     }  
  23.   
  24.     private void closeOpenResultSets() {  
  25.         for (Map.Entry> entry : openResultSets.entrySet()) {  
  26.             DsUtil.close(entry.getValue().get());  
  27.         }  
  28.         openResultSets.clear();  
  29.         openResultSets = null;  
  30.     }  
  31.   
  32.     private void closeUnderlyingStatement() {  
  33.         DsUtil.close(underlyingPreparedStatement);  
  34.         connection = null;  
  35.     }  
  36.     //更多代码见附件  
  37. }  

通过openResultSets持有resultset的软引用, 并且close方法中会先调用closeOpenResultSets把软引用持有的resultset全部关闭, 然后再通过closeUnderlyingStatement去真正关闭statement.


到这里, 所有的事情就完成了. 下面举个栗子.

Java代码  怎样实现关闭connection时自动关闭Statement和ResultSet
  1. @Test  
  2. public void testConnectionNotGood() throws Exception {  
  3.     Connection conn = null;  
  4.     try {  
  5.         conn = new ResultSetStatementAwareConnection(DsUtil.getConnection());  
  6.         PreparedStatement psFoo = conn.prepareStatement("....");  
  7.         ResultSet rsFoo = psFoo.executeQuery();  
  8.         //do something  
  9.         psFoo = conn.prepareStatement("....");  
  10.         rsFoo = psFoo.executeQuery();  
  11.         //do something  
  12.         psFoo = conn.prepareStatement("....");  
  13.         rsFoo = psFoo.executeQuery();  
  14.         //do something  
  15.     } finally {  
  16.         DsUtil.close(conn);  
  17.     }  
  18. }  

我们看到PreparedStatement和ResultSet一共使用了三次, 创建了三个PreparedStatement和三个ResultSet. 但是在finally块中只显式关闭了Connection, 并没有显式关闭PreparedStatement和ResultSet.


不过放心, 虽然没有显式关闭, 但其实三个PrepareStatement和ResultSet都会被自动关闭.