PreparedStatementを複数回再利用する


96

プールのない単一の共通接続でPreparedStatementを使用する場合、準備済みステートメントの能力を維持するすべてのdml / sql操作のインスタンスを再作成できますか?

というのは:

for (int i=0; i<1000; i++) {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
    preparedStatement.close();
}

の代わりに:

PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i=0; i<1000; i++) {
    preparedStatement.clearParameters();
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
}
preparedStatement.close();

私の質問は、このコードをマルチスレッド環境に配置したいという事実から生じます。アドバイスをいただけますか?ありがとう


だからあなたのクエリsqlはループ内で変化しませんか?ループの反復ごとにクエリが変更されない場合、反復PreparedStatementごとに新しいコードを作成するのはなぜですか(最初のコードスニペット)。そうする理由はありますか?
Sabir Khan、

クエリが変更されている場合は、2番目のアプローチの方が適切ですか?欠点はありますか?
Stunner

回答:


144

2番目の方法は少し効率的ですが、はるかに良い方法はそれらをバッチで実行することです。

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
        }

        statement.executeBatch();
    }
}

ただし、JDBCドライバーの実装によって、一度に実行できるバッチの数が決まります。たとえば、1000バッチごとにそれらを実行したい場合があります。

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

マルチスレッド環境については、次のように、try-with-resourcesステートメントを使用した通常のJDBCイディオムに従って、同じメソッドブロック内の接続とステートメントを可能な限り最短の範囲で取得して閉じる場合、このことを心配する必要はありません。上記のスニペット。

これらのバッチがトランザクションの場合、接続の自動コミットをオフにし、すべてのバッチが終了したときにのみトランザクションをコミットします。そうしないと、最初のバッチのバッチが成功したときにデータベースがダーティになる可能性があり、その後のバッチは成功しません。

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);

        try (PreparedStatement statement = connection.prepareStatement(SQL)) {
            // ...

            try {
                connection.commit();
            } catch (SQLException e) {
                connection.rollback();
                throw e;
            }
        }
    }
}

inside the same method block-すべてのスレッドは独自のスタックを持ち、これらの接続とステートメントは一方の側からスタックにあり、別のデータソースからは、executeFunction(== every thread)の新しいインスタンスの呼び出しごとに接続の個別のインスタンスを提供します。私はあなたの言うことを正しく理解していますか?」
Pavel_K '5/10/05

最初の方法を実行していますが、SQLプロファイラーで監視しているときに、1つではなく複数の準備されたステートメントが繰り返し表示されます。複数のステートメントが表示されている理由を理解できませんでした。支援が必要です。
ローグラッドラッド、2015年

ループ内でクエリが変更されない場合、回答は適切です。たとえば、クエリが変更された場合など、クエリが変更された場合はどうなりますか。検証してください
Stunner

13

コードのループは単純化しすぎた例ですよね?

PreparedStatement一度だけ作成し、それをループで何度も再利用することをお勧めします。

それが不可能な状況では(プログラムフローが複雑になりすぎたため)、PreparedStatementサーバー側の作業(SQLの解析と実行のキャッシュ)のため、一度しか使用しなくても、を使用することは依然として有益です。プラン)、まだ削減されます。

Javaサイドを再利用したい状況に対処するためにPreparedStatement、一部のJDBCドライバー(Oracleなど)にはキャッシング機能があります。PreparedStatement同じ接続で同じSQL用にを作成すると、同じ(キャッシュされた) )インスタンス。

マルチスレッドについて:とにかく、JDBC接続を複数のスレッドで共有できる(つまり、複数のスレッドで同時に使用できる)とは思いません。すべてのスレッドは、プールから独自の接続を取得し、それを使用して、再びプールに戻す必要があります。


1
実際、接続には専用のスレッドがあり、すべてのステートメントがその中で実行されますが、準備されたステートメントの公開されたスタックを介してそのスレッドにアクセスします。したがって、他の並行スレッドは最初にすべての準備されたステートメントを構築するために必要なパラメーターのみを渡しますが、その後パラメーターを同時に変更できます
Steel Plume
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.