(0)目次&概説
(1) エラー事象の概要
(1-1) エラーの発生状況
(1-2) エラーメッセージ全文
(2) エラーの原因
(3) エラーの対処方法
(3-1) エラーの修正内容
(3-2) 修正前ソース(Before Fix)
(3-3) 修正前ソース実行結果
(3-3) 修正後ソース(After Fix)
(3-4) 修正後ソース解説
(3-5) 修正後ソース実行結果
(4) 追加で考慮すべき点
(4-1) finally句でclose処理をしていないと、クローズ漏れになる恐れがある
(4-1-1) 明示的にcloseした方が良い理由
(4-2) finally句でclose処理をする場合、SQLExceptionの考慮が必要
(1) エラー事象の概要
(1-1) エラーの発生状況
JDBC接続でサーブレットからDBへの接続時に「java.sql.SQLRecoverableException: Closed Connection」が発生した際の対処方法について記載します。私のケースでは、1つに纏まっていたDB接続の処理を共通部品化するために複数メソッドに分割(Connection、Statement、ResultSet)しようとした所、このエラーに遭遇しました。
java.sql.SQLRecoverableException: Closed Connection
(図1)
(1-2) エラーメッセージの全文
java.sql.SQLRecoverableException: Closed Connection at oracle.jdbc.driver.PhysicalConnection.createStatement(PhysicalConnection.java:3450) at oracle.jdbc.driver.PhysicalConnection.createStatement(PhysicalConnection.java:3425) at login.DbConnectTest3.DbStatement(DbConnectTest3.java:43) at login.DbConnectTest3.doGet(DbConnectTest3.java:72) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:610) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:660) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:798) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:808) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Jan 26, 2020 4:00:55 PM org.apache.catalina.core.StandardWrapperValve invoke SEVERE: Servlet.service() for servlet [login.DbConnectTest3] in context with path [/LoginForms] threw exception java.lang.NullPointerException at login.DbConnectTest3.DbQuery(DbConnectTest3.java:53) at login.DbConnectTest3.doGet(DbConnectTest3.java:73) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:610) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:660) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:798) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:808) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748)
(2) エラーの原因
「try-with-resources」文はそのスコープの範囲を抜ける際にcloseメソッドを自動で呼んでるために「Closed Connection」が発生していました(次の例1・例2)。なので「try-with-resources」の条件文に記載したような、ConnectionオブジェクトやStatementオブジェクトをインスタンス化する部分は、try文を抜けたタイミングでCloseされてしまい、次のメソッドで利用する時にはcloseされていた様です。
(例1)
try(Connection conn_temp = dbms.getConn();){
(例2)
try(Statement stmt = conn.createStatement();){
(3) エラーの対処方法
(3-1) エラーの修正内容
応急対処として、通常の「try-catch」文で記述すればエラーは消えました。
(図2)修正例
左側が「修正前」で、右側が「修正後」です。修正はいずれも「try-with-resources」の条件文に記載していたConnectionオブジェクトやStatementオブジェクトのインスタンス化を、try{ }文の内部に移すという内容です。
No | 修正前 | 修正後 |
1 | try(Connection conn_temp = dbms.getConn();){ conn = conn_temp; |
try{ conn = dbms.getConn(); |
2 | try(Statement stmt = conn.createStatement();){ stmt = stmt_tmp; |
try{ stmt = conn.createStatement(); |
(3-2) 修正前ソース(Before Fix)
package login; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import database.DBMS; /** * Servlet implementation class DbConnectTest1 */ @WebServlet("/DbConnectTest3") public class DbConnectTest3 extends HttpServlet { private static final long serialVersionUID = 1L; public DbConnectTest3() {super();} public Connection DbConnect (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn = null; DBMS dbms = new DBMS(); try(Connection conn_temp = dbms.getConn();){ conn = conn_temp; } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (SQLException e2) { e2.printStackTrace(); } return conn; } public Statement DbStatement (HttpServletRequest request, HttpServletResponse response, Connection conn) throws ServletException, IOException { Statement stmt = null; try(Statement stmt_tmp = conn.createStatement();){ stmt = stmt_tmp; } catch (SQLException e) { e.printStackTrace(); } return stmt; } public ResultSet DbQuery (HttpServletRequest request, HttpServletResponse response, Statement stmt, String sql) throws ServletException, IOException { ResultSet rs = null; try{ rs = stmt.executeQuery(sql); } catch (SQLException e) { e.printStackTrace(); } return rs; } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<html><body> Hello Test !!!<br />"); String select1 = "SELECT * FROM ACCOUNT"; Connection conn1 = null; Statement stmt1 = null; ResultSet rs1 = null; conn1 = DbConnect(request,response); stmt1 = DbStatement(request,response,conn1); rs1 = DbQuery(request,response,stmt1,select1); try { while (rs1.next()) { out.println("AC= "+rs1.getInt("ACCOUNT_ID")+" BAL= "+rs1.getInt("AVAIL_BALANCE")+"<br />"); } } catch (SQLException e) { e.printStackTrace(); } finally { try { if(rs1 != null) rs1.close(); if(stmt1 != null) stmt1.close(); if(conn1 != null) conn1.close(); } catch (SQLException e) { e.printStackTrace(); } } out.println("</body></html>"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
(3-3) 修正前ソース実行結果
(図3)冒頭のエラーが発生
(図4)画面もSQLの結果が表示されず・・
(3-4) 修正後ソース(After Fix)
package login; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import database.DBMS; /** * Servlet implementation class DbConnectTest1 */ @WebServlet("/DbConnectTest2") public class DbConnectTest2 extends HttpServlet { private static final long serialVersionUID = 1L; public DbConnectTest2() {super();} public Connection DbConnect (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn = null; DBMS dbms = new DBMS(); try{ conn = dbms.getConn(); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (SQLException e2) { e2.printStackTrace(); } return conn; } public Statement DbStatement (HttpServletRequest request, HttpServletResponse response, Connection conn) throws ServletException, IOException { Statement stmt = null; try{ stmt = conn.createStatement(); } catch (SQLException e) { e.printStackTrace(); } return stmt; } public ResultSet DbQuery (HttpServletRequest request, HttpServletResponse response, Statement stmt, String sql) throws ServletException, IOException { ResultSet rs = null; try{ rs = stmt.executeQuery(sql); } catch (SQLException e) { e.printStackTrace(); } return rs; } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println("<html><body> Hello Test !!!<br />"); String select1 = "SELECT * FROM ACCOUNT"; Connection conn1 = null; Statement stmt1 = null; ResultSet rs1 = null; conn1 = DbConnect(request,response); stmt1 = DbStatement(request,response,conn1); rs1 = DbQuery(request,response,stmt1,select1); try { while (rs1.next()) { out.println("AC= "+rs1.getInt("ACCOUNT_ID")+" BAL= "+rs1.getInt("AVAIL_BALANCE")+"<br />"); } } catch (SQLException e) { e.printStackTrace(); } finally { try { if(rs1 != null) rs1.close(); if(stmt1 != null) stmt1.close(); if(conn1 != null) conn1.close(); } catch (SQLException e) { e.printStackTrace(); } } out.println("</body></html>"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
(3-4) 修正後ソース解説
行数 | 処理概要 | 備考 |
24行~35行目 | DBMSオブジェクトをインスタンス化し、それを使いConnectionオブジェクトもインスタンス化しています。またtry-catchで例外処理をしており、特にtryには条件文をしてせずに記述しています。 | メソッドの設計方針として、後にcloseしやすいように、Connectionオブジェクトを返り値として返却しています。 (Connection~Statement~ResultSetまで全て行ってしまうと各々を個別にCloseできなくなってしまうため、メソッドを分けている) |
36行~44行目 | ステートメントを作成するメソッドです。 引数にConnectionオブジェクトを貰う形式にしています(メソッドの中でDbConnectを呼ぶのではなく、引数としてConnectionオブジェクトを貰っている)。 この理由は上記と被りますが、後にcloseしやすいようにDbConnectは内部では呼ばず、実際に使うタイミングで直接呼んで、使い終わったらその場で即座にCloseできるようにするためです(内部に埋め込まれているとcloseしにくい)。 |
|
45行~54行目 | ResultSet(クエリの結果)を取得するメソッドです。こちらも同様にtry-catchで例外処理をしており、特にtryには条件文をしてせずに記述しています。 そして上記2メソッドと同じく、引数にStatementオブジェクトを受け取る事で必要な時に呼んでその場でcloseしやすい形式にしました。 |
|
55行~85行目 | doGetメソッドです。 62行~68行目でConnection/Statement/ResultSetの初期化し、メソッドを呼んでインスタンス化しています。 69行~85行目ではtry-catch-finally句でResultSetからSQLの結果を取得しています。Finally句の中で使い終わったConnection/Statement/ResultSetをクローズしています。クローズ時もSQLExceptionの例外発生の可能性があるため、try-catchで囲んでいます。 |
(3-5) 修正後ソース実行結果
(図5)エラーは解消
(図6)SQL結果も表示!
(4) 追加で考慮すべき点
上記の方法でエラー自体は消えるものの、他にも考慮すべき点を記載します。
(4-1) finally句でclose処理をしていないと、クローズ漏れになる恐れがある
基本はConnectionオブジェクト、Statementオブジェクト、ResultSetオブジェクトを全てリリースします。なので「try-with-resources」を使わない場合は明示的にclose()を行う必要があります。
(4-1-1) 明示的にcloseした方が良い理由
Statementを作成するとDBによってはカーソルがOpenになりますが、Closeされるまではメモリが確保され続け、DB側でもカーソルが開きっぱなしになってしまうので閉じる必要があります。
通常は上位オブジェクトを閉じれば、下位オブジェクトも自動的に閉じられる機能があるものの(Connection>Statement>ResultSet 左に記載ほど上位)、以前OracleでConnectionを閉じたにも関わらずカーソル(ResultSet)が開いたままになり「Maximum open cursors exceeded.」のようなエラーが発生する事象があったため、ResultSetやStatementも明示的に閉じる方が安全と認識されるようになりました。
(4-2) finally句でclose処理をする場合、SQLExceptionの考慮が必要
finally句でclose処理をする場合、クローズ不可時の例外としてSQLExceptionをスローするため、クローズの処理もtry、catchで囲む必要があります。