16

私は最近、基本的な 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 つの質問があります。

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

1.B) 教授は、明示的に ResultSet を閉じるように私に何度も要求します (Statement のドキュメントで ResultSet を閉じることが明確になっている場合でも)。Sun はそれを推奨していると彼女は言います。そうする理由はありますか?

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

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) このコードは正しいですか? 私の教授は Re​​sultSet の明示的なクローズがないため、この方法を好まないと思いますが、ドキュメントですべてがクローズされていることが明らかである限り、彼女はそれで問題ないと私に言いました。同様の例で公式ドキュメントへのリンクを提供できますか、またはドキュメントに基づいて、このコードに問題がないことを示していますか?

4

6 に答える 6

7

JDBC コードの面白いところは、実装がどの程度準拠しているかが必ずしも明確ではない仕様に合わせてコーディングしていることです。多くの異なるデータベースとドライバーがあり、一部のドライバーは他のドライバーよりも動作が優れています。これは、すべてを明示的に閉じるなどのことを推奨して、注意を怠る傾向があります。ここで接続だけを閉じても問題ありません。安全のためにresultSetを閉じることは、議論するのが難しいです。ここで使用しているデータベースまたはドライバーを示していません。一部の実装では有効ではない可能性があるドライバーに関する仮定をハードコーディングしたくありません。

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

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

また、これの冗長さに反対している場合、その答えは、戦略、resultSet マッパー、準備されたステートメント セッターなどを使用する再利用可能なユーティリティにコードを隠すことです。もちろん、これはすべて以前に行われています。Spring JDBC の再発明への道を歩むことになります。

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

于 2014-03-26T20:17:12.300 に答える
2

これは、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 は、get が割り当てられた with resources ブロックに含まれるすべてのリソースを保証するという点で、上記のロジック フローに従います。閉まっている。with resources ブロックでスローされた例外はすべてスローされますが、割り当てられたリソースは引き続き閉じられます。このコードは非常に単純化されており、次のようになります。

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 割り当てをリソース ブロックに移動しました。実際には、この単純なソリューションは効率的ではないため、実際には機能しません。接続の確立は非常にコストのかかる操作であるため、接続を再利用する必要があります。さらに、この単純な例では、クエリ パラメータを準備済みステートメントに割り当てません。リソース ブロックには割り当てステートメントのみを含める必要があるため、これらのシナリオの処理には注意が必要です。これを説明するために、別の例も追加しました

   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;
   }
于 2015-10-22T17:22:47.397 に答える
0

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

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);
    }
}
于 2014-03-26T20:34:57.417 に答える