web-dev-qa-db-ja.com

PreparedStatementを複数回再利用する

プールなしの単一の共通接続で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();

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

95
Steel Plume

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;
            }
        }
    }
}
140
BalusC

コードのループは、単純化した例にすぎませんか?

PreparedStatementを1回だけ作成し、ループ内で何度も再利用する方が良いでしょう。

それが不可能な状況では(プログラムフローが複雑になりすぎるため)、サーバー側の作業(SQLの解析とキャッシュ)のため、1回だけ使用する場合でも、PreparedStatementを使用することは有益です。実行計画)、まだ削減されます。

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

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

13
Thilo