怎樣實現關閉connection時自動關閉Statement和ResultSet

一劍平江湖發表於2017-07-27


轉自:怎樣實現關閉connection時自動關閉Statement和ResultSet


關閉資料連線時, 一定也要關閉Statement和ResultSet, 不然在併發量較大的時候可能導致記憶體洩漏. (如果是用tomcat自帶的資料來源實現, 則可以通過新增interceptor實現自動關閉statement.)


但是, 關閉Statement和ResultSet是乏味的工作.


例如下面的程式碼:

Java程式碼  收藏程式碼
  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程式碼  收藏程式碼
  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程式碼  收藏程式碼
  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程式碼  收藏程式碼
  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程式碼  收藏程式碼
  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程式碼  收藏程式碼
  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都會被自動關閉.

相關文章