Menggunakan kembali PreparedStatement beberapa kali

97

dalam kasus menggunakan PreparedStatement dengan satu koneksi umum tanpa kumpulan apa pun, dapatkah saya membuat ulang instance untuk setiap operasi dml / sql yang mempertahankan kekuatan pernyataan yang disiapkan?

Maksudku:

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

dari pada:

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

pertanyaan saya muncul karena saya ingin memasukkan kode ini ke dalam lingkungan multithread, dapatkah Anda memberi saya beberapa saran? Terima kasih

Plume Baja
sumber
jadi kueri Anda, sqltidak berubah dengan in the loop? jika kueri itu tidak berubah untuk setiap iterasi loop, lalu mengapa Anda membuat yang baru PreparedStatementuntuk setiap iterasi (di cuplikan kode pertama)? Apakah ada alasan untuk melakukannya?
Sabir Khan
katakanlah jika kueri berubah, maka masih pendekatan kedua lebih baik kan? Ada kerugian?
Cantik

Jawaban:

144

Cara kedua sedikit lebih efisien, tetapi cara yang jauh lebih baik adalah mengeksekusinya secara berkelompok:

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();
    }
}

Namun, Anda bergantung pada implementasi driver JDBC, berapa banyak batch yang dapat Anda jalankan sekaligus. Misalnya, Anda mungkin ingin mengeksekusinya setiap 1000 batch:

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.
            }
        }
    }
}

Mengenai lingkungan multithread, Anda tidak perlu khawatir tentang hal ini jika Anda memperoleh dan menutup koneksi dan pernyataan dalam cakupan sesingkat mungkin di dalam blok metode yang sama sesuai dengan idiom JDBC normal menggunakan pernyataan coba-dengan-sumber daya seperti yang ditunjukkan di cuplikan di atas.

Jika kumpulan tersebut bersifat transaksional, maka Anda ingin menonaktifkan koneksi otomatis dan hanya melakukan transaksi saat semua kumpulan selesai. Jika tidak, hal itu dapat mengakibatkan database kotor ketika kelompok pertama dari batch berhasil dan kemudian tidak.

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;
            }
        }
    }
}
BalusC
sumber
inside the same method block- maksud Anda bahwa setiap utas akan memiliki tumpukannya sendiri dan koneksi serta pernyataan ini berada dalam tumpukan dari satu sisi dan dari sumber data lain akan memberikan setiap panggilan baru dari executeFunction (== every thread) instance koneksi yang terpisah. Apakah saya mengerti Anda benar? "
Pavel_K
Saya melakukan metode pertama tetapi saat memantau di SQL profiler saya melihat beberapa pernyataan yang disiapkan berulang daripada satu. Saya tidak tahu mengapa itu menunjukkan banyak pernyataan. bantuan yang dibutuhkan.
bocah nakal
Jawaban Anda bagus asalkan kueri tidak berubah dalam putaran .. bagaimana jika kueri berubah misalnya dalam kasus saya di mana kueri diubah .. saya berasumsi masih pendekatan kedua lebih baik. harap validasi
Stunner
13

Loop dalam kode Anda hanyalah contoh yang terlalu disederhanakan, bukan?

Akan lebih baik untuk membuat PreparedStatementsekali saja, dan menggunakannya kembali berulang kali dalam loop.

Dalam situasi di mana itu tidak mungkin (karena terlalu rumit aliran program), masih bermanfaat untuk menggunakan PreparedStatement, bahkan jika Anda menggunakannya hanya sekali, karena pekerjaan sisi server (parsing SQL dan caching eksekusi rencana), masih akan dikurangi.

Untuk mengatasi situasi di mana Anda ingin menggunakan kembali sisi Java PreparedStatement, beberapa driver JDBC (seperti Oracle) memiliki fitur caching: Jika Anda membuat PreparedStatementSQL yang sama pada koneksi yang sama, itu akan memberi Anda yang sama (cache ) contoh.

Tentang multi-threading: Saya rasa koneksi JDBC tidak dapat digunakan bersama di beberapa utas (misalnya, digunakan secara bersamaan oleh beberapa utas). Setiap utas harus mendapatkan koneksi sendiri dari kolam, menggunakannya, dan mengembalikannya ke kolam lagi.

Thilo
sumber
1
Sebenarnya koneksi memiliki utas eksklusif dan setiap pernyataan dieksekusi di dalamnya, tetapi saya mengakses melalui tumpukan pernyataan siap yang terbuka ke utas itu. Jadi utas konkuren lainnya awalnya hanya meneruskan parameter yang diperlukan untuk membuat semua pernyataan yang disiapkan, tetapi kemudian mereka dapat mengubah parameter secara bersamaan
Steel Plume