Java: Masukkan beberapa baris ke MySQL dengan PreparedStatement

88

Saya ingin memasukkan beberapa baris ke dalam tabel MySQL sekaligus menggunakan Java. Jumlah baris dinamis. Di masa lalu saya melakukan ...

for (String element : array) {
    myStatement.setString(1, element[0]);
    myStatement.setString(2, element[1]);

    myStatement.executeUpdate();
}

Saya ingin mengoptimalkan ini untuk menggunakan sintaks yang didukung MySQL:

INSERT INTO table (col1, col2) VALUES ('val1', 'val2'), ('val1', 'val2')[, ...]

tetapi dengan PreparedStatementsaya tidak tahu cara untuk melakukan ini karena saya tidak tahu sebelumnya berapa banyak elemen yang arrayakan mengandung. Jika tidak mungkin dengan a PreparedStatement, bagaimana lagi saya bisa melakukannya (dan masih lolos dari nilai dalam larik)?

Tom Marthenal
sumber

Jawaban:

178

Anda dapat membuat batch PreparedStatement#addBatch()dan menjalankannya dengan PreparedStatement#executeBatch().

Inilah contoh awal:

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

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

            statement.addBatch();
            i++;

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

Ini dijalankan setiap 1000 item karena beberapa driver JDBC dan / atau DB mungkin memiliki batasan pada panjang batch.

Lihat juga :

BalusC
sumber
26
Sisipan Anda akan lebih cepat jika Anda memasukkannya ke dalam transaksi ... yaitu bungkus dengan connection.setAutoCommit(false);dan connection.commit(); unduh.oracle.com/javase/tutorial/jdbc/basics/…
Joshua Martell
1
Sepertinya kamu bisa mengeksekusi batch kosong jika ada 999 item.
djechlin
2
@electricalbah itu akan dijalankan secara normal karenai == entities.size()
Yohanes AI
Berikut sumber daya lain yang bagus untuk menyatukan pekerjaan batch menggunakan pernyataan yang disiapkan. viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis
1
@ AndréPaulo: Hanya SQL INSERT yang cocok untuk pernyataan yang disiapkan. Lihat tautan tutorial JDBC untuk contoh dasar. Ini tidak terkait dengan pertanyaan konkret.
BalusC
32

Ketika driver MySQL digunakan, Anda harus mengatur parameter koneksi rewriteBatchedStatementske true ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

Dengan parameter ini pernyataan ditulis ulang menjadi penyisipan massal ketika tabel dikunci hanya sekali dan indeks diperbarui hanya sekali. Jadi jauh lebih cepat.

Tanpa parameter ini, satu-satunya keuntungan adalah kode sumber yang lebih bersih.

MichalSv
sumber
ini adalah komentar untuk performence untuk konstruksi: statement.addBatch (); if ((i + 1)% 1000 == 0) {statement.executeBatch (); // Jalankan setiap 1000 item. }
MichalSv
Rupanya driver MySQL memiliki bug bugs.mysql.com/bug.php?id=71528 Ini juga menyebabkan masalah untuk kerangka kerja ORM seperti Hibernate hibernate.atlassian.net/browse/HHH-9134
Shailendra
Iya. Ini juga benar untuk saat ini. Setidaknya untuk 5.1.45versi konektor mysql.
v.ladynev
<artifactId> mysql-connector-java </artifactId> <version> 8.0.14 </version> Baru saja diperiksa apakah benar 8.0.14. Tanpa menambahkan rewriteBatchedStatements=truetidak ada perolehan kinerja.
vincent mathew
8

Jika Anda dapat membuat pernyataan sql Anda secara dinamis, Anda dapat melakukan solusi berikut:

String myArray[][] = { { "1-1", "1-2" }, { "2-1", "2-2" }, { "3-1", "3-2" } };

StringBuffer mySql = new StringBuffer("insert into MyTable (col1, col2) values (?, ?)");

for (int i = 0; i < myArray.length - 1; i++) {
    mySql.append(", (?, ?)");
}

myStatement = myConnection.prepareStatement(mySql.toString());

for (int i = 0; i < myArray.length; i++) {
    myStatement.setString(i, myArray[i][1]);
    myStatement.setString(i, myArray[i][2]);
}
myStatement.executeUpdate();
Ali Shakiba
sumber
Saya yakin jawaban yang diterima jauh lebih baik !! Saya tidak tahu tentang pembaruan batch dan ketika saya mulai menulis jawaban ini, jawaban itu belum dikirimkan !!! :)
Ali Shakiba
Pendekatan ini jauh lebih cepat daripada pendekatan yang diterima. Saya mengujinya, tetapi tidak menemukan alasannya. @JohnS apa kamu tahu kenapa?
julian0zzx
@ julian0zzx tidak, tapi mungkin karena itu dijalankan sebagai sql tunggal, bukan multipel. tapi saya tidak yakin.
Ali Shakiba
3

Jika Anda memiliki penambahan otomatis dalam tabel dan perlu mengaksesnya .. Anda dapat menggunakan pendekatan berikut ... Lakukan pengujian sebelum menggunakan karena getGeneratedKeys () dalam Pernyataan karena bergantung pada driver yang digunakan. Kode di bawah ini diuji pada Maria DB 10.0.12 dan Maria JDBC driver 1.2

Ingatlah bahwa meningkatkan ukuran tumpukan meningkatkan kinerja hanya sampai batas tertentu ... karena penyiapan saya, meningkatkan ukuran tumpukan di atas 500 sebenarnya menurunkan kinerja.

public Connection getConnection(boolean autoCommit) throws SQLException {
    Connection conn = dataSource.getConnection();
    conn.setAutoCommit(autoCommit);
    return conn;
}

private void testBatchInsert(int count, int maxBatchSize) {
    String querySql = "insert into batch_test(keyword) values(?)";
    try {
        Connection connection = getConnection(false);
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        boolean success = true;
        int[] executeResult = null;
        try {
            pstmt = connection.prepareStatement(querySql, Statement.RETURN_GENERATED_KEYS);
            for (int i = 0; i < count; i++) {
                pstmt.setString(1, UUID.randomUUID().toString());
                pstmt.addBatch();
                if ((i + 1) % maxBatchSize == 0 || (i + 1) == count) {
                    executeResult = pstmt.executeBatch();
                }
            }
            ResultSet ids = pstmt.getGeneratedKeys();
            for (int i = 0; i < executeResult.length; i++) {
                ids.next();
                if (executeResult[i] == 1) {
                    System.out.println("Execute Result: " + i + ", Update Count: " + executeResult[i] + ", id: "
                            + ids.getLong(1));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            success = false;
        } finally {
            if (rs != null) {
                rs.close();
            }
            if (pstmt != null) {
                pstmt.close();
            }
            if (connection != null) {
                if (success) {
                    connection.commit();
                } else {
                    connection.rollback();
                }
                connection.close();
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
budak
sumber
3

@Ali Shakiba kode Anda perlu beberapa modifikasi. Bagian kesalahan:

for (int i = 0; i < myArray.length; i++) {
     myStatement.setString(i, myArray[i][1]);
     myStatement.setString(i, myArray[i][2]);
}

Kode yang diperbarui:

String myArray[][] = {
    {"1-1", "1-2"},
    {"2-1", "2-2"},
    {"3-1", "3-2"}
};

StringBuffer mySql = new StringBuffer("insert into MyTable (col1, col2) values (?, ?)");

for (int i = 0; i < myArray.length - 1; i++) {
    mySql.append(", (?, ?)");
}

mysql.append(";"); //also add the terminator at the end of sql statement
myStatement = myConnection.prepareStatement(mySql.toString());

for (int i = 0; i < myArray.length; i++) {
    myStatement.setString((2 * i) + 1, myArray[i][1]);
    myStatement.setString((2 * i) + 2, myArray[i][2]);
}

myStatement.executeUpdate();
vinay
sumber
Ini adalah pendekatan yang jauh lebih cepat dan lebih baik dalam keseluruhan jawaban. Ini harus menjadi jawaban yang diterima
Arun Shankar
1
Seperti yang disebutkan dalam jawaban yang diterima, beberapa driver / database JDBC memiliki batasan jumlah baris yang dapat Anda sertakan dalam pernyataan INSERT. Dalam kasus contoh di atas, jika myArraymemiliki panjang yang lebih besar dari batas tersebut, Anda akan mendapatkan pengecualian. Dalam kasus saya, saya memiliki batas 1.000 baris yang memunculkan kebutuhan untuk eksekusi batch, karena saya berpotensi memperbarui lebih dari 1.000 baris pada proses tertentu. Jenis pernyataan ini secara teoritis akan berfungsi dengan baik jika Anda tahu Anda memasukkan kurang dari jumlah maksimum yang diperbolehkan. Sesuatu yang perlu diingat.
Danny Bullis
Untuk memperjelas, jawaban di atas menyebutkan batasan driver / database JDBC pada panjang batch, tetapi mungkin juga ada batasan jumlah baris yang disertakan dalam pernyataan sisipan, seperti yang telah saya lihat dalam kasus saya.
Danny Bullis
0

kami dapat mengirimkan beberapa pembaruan bersama-sama di JDBC untuk mengirimkan pembaruan batch.

kita dapat menggunakan objek Statement, PreparedStatement, dan CallableStatement untuk update bacth dengan menonaktifkan autocommit

Fungsi addBatch () dan executeBatch () tersedia dengan semua objek pernyataan untuk memiliki BatchUpdate

di sini metode addBatch () menambahkan satu set pernyataan atau parameter ke batch saat ini.

kapil das
sumber