web-dev-qa-db-ja.com

Try / Try-with-resourcesおよびConnection、Statement、ResultSetの終了

私は最近、基本的なjdbc接続スキームの処理方法について教授といくつかの話し合いをしました。 2つのクエリを実行するとします。これが彼の提案です

_public void doQueries() throws MyException{
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
        PreparedStatement s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

        rs.close();
        s2.close();
        s1.close();
    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't really do anything
        }
    }
}
_

私はこのアプローチが好きではありません。それについて2つの質問があります。

1.A)「他のこと」を行う場所、またはrs.close()またはs2.close()で例外がスローされた場合、_s1_にはメソッドが終了すると閉じられます。私はそれについて正しいですか?

1.B)教授は、ResultSetを明示的に閉じるように要求し続けます(StatementのドキュメントでResultSetを閉じることが明確に示されている場合でも)。そうする理由はありますか?

これが同じことの正しいコードだと私が思うものです:

_public void doQueries() throws MyException{
    Connection con = null;
    PreparedStatement s1 = null;
    PreparedStatement s2 = null;
    try {
        con = DriverManager.getConnection(dataSource);
        s1 = con.prepareStatement(updateSqlQuery);
        s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (s2 != null) {
                s2.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (s1 != null) {
                s1.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't do nothing
        }
    }
}
_

2.A)このコードは正しいですか? (メソッドが終了すると、すべてが閉じられることが保証されていますか?)

2.B)これは非常に大きくて冗長です(ステートメントが多いとさらに悪くなります)try-with-resourcesを使用せずにこれを行うより短いまたはよりエレガントな方法はありますか?

最後にこれは私が最も好きなコードです

_public void doQueries() throws MyException{
    try (Connection con = DriverManager.getConnection(dataSource);
         PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         PreparedStatement s2 = con.prepareStatement(selectSqlQuery))
    {

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    }
}
_

3)このコードは正しいですか?私の教授はResultSetが明示的に閉じられていないため、この方法は気に入らないと思いますが、ドキュメントですべてが閉じられていることが明らかである限り、彼女は問題ないと言っています。同様の例を含む公式ドキュメントへのリンクを提供できますか、またはドキュメントに基づいて、このコードに問題がないことを示していますか?

13
José D.

tl; dr

  • 理論的には、ステートメントを閉じると結果セットが閉じます。
  • 実際には、一部の障害のあるJDBCドライバー実装は、悪名高く、そうすることに失敗しました。したがって、彼女がSchool of Hard Knocksから学んだインストラクターからのアドバイス。アプリにデプロイされる可能性があるすべての JDBCドライバー のすべての実装に精通している場合を除き、 try-with-resources を使用して、 [〜 #〜] jdbc [〜#〜] ステートメントや結果セットなどの作業。

Try-with-resources構文を使用する

どのコードも try-with-resources を使用して完全にです。 try-with-resources構文では、ConnectionPreparedStatement、およびResultSetを括弧で囲んで、中括弧の前に宣言してインスタンス化します。 Oracleによるチュートリアル を参照してください。

最後のコード例ではResultSetが明示的に閉じられていませんが、ステートメントが閉じられているときは間接的に閉じる必要があります。ただし、以下で説明するように、JDBCドライバーに障害があるため、閉じられない可能性があります

AutoCloseable

AutoCloseable を実装するそのようなオブジェクトは、closeメソッドが自動的に呼び出されます。したがって、これらのfinally句は必要ありません。

これを読む人文科学の主要な人たちのために、はい、Javaチームは「閉鎖可能」のスペルを間違えました。

自動クローズ可能なオブジェクトとそうでないオブジェクトをどのようにして知るのですか? AutoCloseable がスーパーインターフェイスとして宣言されているかどうかを確認するには、クラスのドキュメントを参照してください。逆に、バンドルされているすべてのサブインターフェースと実装クラス(実際には数十)のリストについては、 AutoCloseable のJavaDocページを参照してください。

たとえば、SQL作業の場合、 ConnectionStatementPreparedStatementResultSet 、および RowSet すべて自動閉鎖可能ですが、 DataSource はそうではありません。 DataSourceは潜在的なリソース(データベース接続)に関するデータを格納しますが、それ自体はリソースではないため、これは理にかなっています。 DataSourceは決して「オープン」されないため、クローズする必要はありません。

Oracleチュートリアル、 、try-with-resourcesステートメント を参照してください。

コード例

最後のコード例は間近に迫っていますが、ResultSetをtry-with-resourcesステートメントでラップして、自動的に閉じる必要があります。

ResultSet JavaDocを引用するには:

ResultSetオブジェクトは、それを生成したStatementオブジェクトがクローズ、再実行、または複数の結果のシーケンスから次の結果を取得するために使用されると、自動的にクローズされます。

先生が示唆しているように、一部のJDBCドライバーには、ResultSetまたはStatementが閉じているときにPreparedStatementを閉じるというJDBC仕様の約束に沿うことができない重大な欠陥があります。多くのプログラマーは、各ResultSetオブジェクトを明示的に閉じる習慣をつけています。

この追加の義務は、try-with-resources構文で簡単になりました。実際の作業では、とにかくAutoCloseableなどのすべてのResultSetオブジェクトをtry-elseする可能性があります。だから私自身の意見は:try-with-resources + elseにしてみませんか?害を及ぼさず、コードが意図について自己文書化し、コードがこれらの障害のあるJDBCドライバーの1つに遭遇した場合に役立つ可能性があります唯一のコストは、とにかくtry-catch-elseを配置していると仮定すると、ペアの括弧です。

Oracleチュートリアル で述べたように、一緒に宣言された複数のAutoCloseableオブジェクトは、必要に応じて逆順で閉じられます。

ヒント:try-with-resources構文では、最後に宣言されたリソース項目でオプションのセミコロンを使用できます。セミコロンは、よく読みやすく、一貫性があり、カットアンドペーストの編集が容易であるため、習慣としてセミコロンを含めています。 PreparedStatement s2行。

public void doQueries() throws MyException{
    // First try-with-resources.
    try ( Connection con = DriverManager.getConnection( dataSource ) ;
          PreparedStatement s1 = con.prepareStatement( updateSqlQuery ) ;
          PreparedStatement s2 = con.prepareStatement( selectSqlQuery ) ;
    ) {

        … Set parameters of PreparedStatements, etc.

        s1.executeUpdate() ;

        // Second try-with-resources, nested within first.
        try (
            ResultSet rs = s2.executeQuery() ;
        ) {
            … process ResultSet
        } catch ( SQLException e2 ) {  
            … handle exception related to ResultSet.
        }

    } catch ( SQLException e ) {  
        … handle exception related to Connection or PreparedStatements.
    }
}

この種の作業には、将来のプログラミング言語で発明される可能性のあるよりエレガントな構文があると思います。しかし、今のところ、リソースの試用版があり、私はそれを楽しく使用しています。 try-with-resourcesは完全にはエレガントではありませんが、以前の構文を大幅に改善しています。

ちなみに、オラクルは、コードに見られる DataSource アプローチではなく、 DriverManager 実装を使用して接続を取得することをお勧めします。コード全体でDataSourceを使用すると、ドライバーの切り替えや接続プールへの切り替えが簡単になります。 JDBCドライバーがDataSourceの実装を提供しているかどうかを確認します。

更新:Java 9

Java 9では、try-with-resourcesの前に前にリソースを初期化できますこの記事を参照してください 。この柔軟性いくつかのシナリオで役立つ場合があります。

15
Basil Bourque

JDBCコードの楽しい点は、実装がどの程度準拠しているかが常に明確ではない仕様にコーディングしていることです。多くの異なるデータベースとドライバーがあり、一部のドライバーは他よりも動作が優れています。それは人々を警告の側に誤解させる傾向があり、すべてを明示的に閉じるようなことを勧めます。ここで接続のみを閉じても問題ありません。安全のために結果セットを閉じることは、議論するのが難しいです。ここで使用しているデータベースまたはドライバーを指定しないでください。一部の実装では有効ではない可能性があるドライバーについての仮定をハードコーディングしたくありません。

順番に閉じると、例外がスローされ、一部のクローズがスキップされる可能性があるという問題が発生します。あなたはそれを心配する権利があります。

これはおもちゃの例です。ほとんどの実際のコードは接続プールを使用します。この場合、closeメソッドを呼び出しても実際には接続が閉じられず、代わりに接続がプールに返されます。したがって、プールを使用すると、リソースが閉じられない可能性があります。このコードを変更して接続プールを使用する場合は、戻って少なくともステートメントを閉じる必要があります。

また、これの冗長性に異議を唱えている場合、その答えは、戦略、resultSetマッパー、準備されたステートメントセッターなどを使用する再利用可能なユーティリティでコードを非表示にすることです。これはすべて以前に行われたことです。あなたはSpring JDBCを再発明する道にいるでしょう。

そういえば、Spring JDBCはすべてを明示的に閉じます(おそらく、可能な限り多くのドライバーで動作する必要があり、一部のドライバーが正常に動作していないために問題を引き起こしたくないためです)。

5
Nathan Hughes

これが、try-with-resourcesの主な動機です。参照としてJava tutorials を参照してください。教授は時代遅れです。結果セットの問題に対処したい場合は、いつでも別の試みでそれを囲むことができます-with-resourcesステートメント。

1
Martin Serrano

これは、JDBCのようなリソースを処理するための最良のソリューションであると私が見つけたものです。このメソッドは、最終的な変数を活用して不変の機能を提供し、必要な場合にのみこれらの変数を宣言して割り当てることで、CPU効率が非常に高くなり、割り当てられて開かれたすべてのリソースが状態に関係なく閉じられることが保証されます。例外の。使用している手法は、すべてのシナリオに対処するために注意深く実装されていないと、リソースリークを引き起こす可能性があるギャップを残します。この手法では、パターンが常に守られている限り、リソースリークは発生しません。
1)リソースを割り当て
2)試す
3)リソースを使用
4)最後にリソースを閉じる

public void doQueries() throws MyException {
   try {
      final Connection con = DriverManager.getConnection(dataSource);
      try {
         final PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         try {

            // Set the parameters of the PreparedStatements and maybe do other things

            s1.executeUpdate();

         } finally {
            try { s1.close(); } catch (SQLException e) {}
         }

         final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
         try {

            // Set the parameters of the PreparedStatements and maybe do other things

            final ResultSet rs = s2.executeQuery();
            try {

               // Do something with rs

            } finally {
               try { rs.close(); } catch (SQLException e) {}
            }
         } finally {
            try { s2.close(); } catch (SQLException e) {}
         }
      } finally {
         try { con.close(); } catch (SQLException e) {}
      }
   } catch (SQLException e) {
      throw new MyException(e);
   }
}

Java 7を使用すると、新しいtry -with-resourcesを活用して、これをさらに簡素化できます。新しいtry -with-resourcesは上記のロジックフローに従い、すべてのリソースが含まれることを保証します割り当てられたwith resourcesブロックでは閉じられます。withresourcesブロックでスローされた例外はスローされますが、割り当てられたリソースは引き続き閉じられます。このコードは大幅に簡略化され、次のようになります:

public void doQueries() throws MyException {
   try (
      final Connection con = DriverManager.getConnection(dataSource);
      final PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
      final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
      final ResultSet rs = s2.executeQuery()) {

      s1.executeUpdate();

         // Do something with rs

   } catch (SQLException e) {
      throw new MyException(e);
   }
}

[編集]:rs割り当てをリソースブロックに移動し、最も単純な実装を示しました。これは効率的ではないため、実際には、この単純なソリューションは実際には機能しません。接続の確立は非常にコストのかかる操作であるため、接続すべきを再利用します。また、この簡単な例では、準備されたステートメントにクエリパラメータを割り当てていません。リソースブロックshouldには代入ステートメントのみが含まれるため、これらのシナリオを処理するように注意する必要があります。これを表現するために、別の例も追加しました

   public void doQueries() throws MyException {

      final String updateSqlQuery = "select @@servername";
      final String selecSqlQuery  = "select * from mytable where col1 = ? and col2 > ?";
      final Object[] queryParams  = {"somevalue", 1};

      try (final Connection con = DriverManager.getConnection(dataSource);
         final PreparedStatement s1 = newPreparedStatement(con, updateSqlQuery);
         final PreparedStatement s2 = newPreparedStatement(con, selectSqlQuery, queryParams);
         final ResultSet rs = s2.executeQuery()) {

         s1.executeUpdate();

         while (!rs.next()) {
            // do something with the db record.
         }
      } catch (SQLException e) {
         throw new MyException(e);
      }
   }

   private static PreparedStatement newPreparedStatement(Connection con, String sql, Object... args) throws SQLException
   {
      final PreparedStatement stmt = con.prepareStatement(sql);
      for (int i = 0; i < args.length; i++)
         stmt.setObject(i, args[i]);
      return stmt;
   }
0
Armand

これらのリソースのクローズを処理するutilクラスを作成できます。つまり、私はSQLExceptionを無視して、utilクラスのリソースを閉じようとしましたが、必要に応じて、ログを収集したり、コレクション内のリソースを閉じたら、それらを収集してスローすることができます

public class DBUtil {
public static void closeConnections(Connection ...connections){
    if(connections != null ){
        for(Connection conn : connections){
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

public static void closeResultSets(ResultSet ...resultSets){
    if(resultSets != null ){
        for(ResultSet rs: resultSets){
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

public static void closeStatements(Statement ...statements){
    if(statements != null){
        for(Statement statement : statements){
            if(statement != null){
                try {
                    statement.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

}

そして、あなたのメソッドからそれを呼び出すだけです:

    public void doQueries() throws MyException {
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = null;
        PreparedStatement s2 = null;
        try {
            s1 = con.prepareStatement(updateSqlQuery);
            s2 = con.prepareStatement(selectSqlQuery);

            // Set the parameters of the PreparedStatements and maybe do other things
            s1.executeUpdate();
            ResultSet rs = null;
            try {
                rs = s2.executeQuery();
            } finally {
                DBUtil.closeResultSets(rs);
            }
        } finally {
            DBUtil.closeStatements(s2, s1);
        }

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        DBUtil.closeConnections(con);
    }
}
0
nbsp