(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で囲む必要があります。
