Java: PreparedStatement ile MySQL'e birden çok satır ekleyin


88

Java kullanarak bir MySQL tablosuna aynı anda birden çok satır eklemek istiyorum. Satır sayısı dinamiktir. Geçmişte yapıyordum ...

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

    myStatement.executeUpdate();
}

MySQL destekli sözdizimini kullanmak için bunu optimize etmek istiyorum:

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

ama a ile PreparedStatementbunu yapmanın herhangi bir yolunu bilmiyorum çünkü önceden kaç element arrayiçereceğini bilmiyorum . A ile mümkün değilse, bunu PreparedStatementbaşka nasıl yapabilirim (ve yine de dizideki değerlerden kaçabilirim)?

Yanıtlar:


178

Tarafından bir parti oluşturabilir PreparedStatement#addBatch()ve yürütebilirsiniz PreparedStatement#executeBatch().

İşte bir başlangıç ​​örneği:

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

Her 1000 öğede bir yürütülür çünkü bazı JDBC sürücüleri ve / veya DB'ler parti uzunluğunda bir sınırlamaya sahip olabilir.

Ayrıca bakınız :


26
Bunları işlemlere koyarsanız eklemeleriniz daha hızlı gidecektir ... ör. Sarın connection.setAutoCommit(false);ve connection.commit(); download.oracle.com/javase/tutorial/jdbc/basics/…
Joshua Martell

1
Görünüşe göre 999 öğe varsa boş bir parti yürütebilirsiniz.
djechlin

2
@electricalbah normal şekilde yürütülecek çünküi == entities.size()
Yohanes AI

Hazırlanmış ifadeleri kullanarak toplu işleri bir araya getirmek için başka bir iyi kaynak. viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis

1
@ AndréPaulo: Hazırlanmış bir ifade için uygun herhangi bir SQL INSERT. Temel örnekler için JDBC öğretici bağlantılarına bakın. Bu somut soruyla ilgili değil.
BalusC

32

MySQL sürücüsü kullanıldığında, bağlantı parametresini rewriteBatchedStatementstrue olarak ayarlamanız gerekir ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

Bu parametre ile, tablo yalnızca bir kez kilitlendiğinde ve dizinler yalnızca bir kez güncellendiğinde ifade toplu eklemeye yeniden yazılır. Yani çok daha hızlı.

Bu parametre olmadan tek avantaj, daha temiz kaynak kodudur.


bu, yapım için performansın yorumudur: statement.addBatch (); eğer ((i + 1)% 1000 == 0) {ifade.executeBatch (); // Her 1000 öğeyi yürütün. }
MichalSv

Görünüşe göre MySQL sürücüsünde bir hata var bugs.mysql.com/bug.php?id=71528 Bu ayrıca Hibernate hibernate.atlassian.net/browse/HHH-9134
Shailendra

Evet. Bu şimdilik de doğru. En azından 5.1.45mysql bağlayıcı sürümü için.
v.ladynev

<artifactId> mysql-connector-java </artifactId> <version> 8.0.14 </version> 8.0.14'ün doğruluğunu kontrol ettim. Eklemeden rewriteBatchedStatements=trueperformans kazancı olmaz.
vincent mathew

8

Sql ifadenizi dinamik olarak oluşturabiliyorsanız, aşağıdaki geçici çözümü yapabilirsiniz:

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

Kabul edilen cevabın çok daha iyi olduğuna inanıyorum !! Toplu güncellemeleri bilmiyordum ve bu cevabı yazmaya başladığımda bu cevap henüz gönderilmedi !!! :)
Ali Shakiba

Bu yaklaşım, kabul edilenden çok daha hızlıdır. Test ediyorum ama nedenini bulamıyorum. @JohnS neden biliyor musun?
julian0zzx

@ julian0zzx hayır, ancak çoklu yerine tek bir sql olarak yürütüldüğü için olabilir. ama emin değilim.
Ali Shakiba

3

Tabloda otomatik artışınız varsa ve ona erişmeniz gerekiyorsa, aşağıdaki yaklaşımı kullanabilirsiniz ... Kullanmadan önce test yapın çünkü İfadede getGeneratedKeys () kullanılan sürücüye bağlıdır çünkü. Aşağıdaki kod Maria DB 10.0.12 ve Maria JDBC sürücüsü 1.2 üzerinde test edilmiştir.

Toplu iş boyutunu artırmanın performansı yalnızca belirli bir dereceye kadar iyileştirdiğini unutmayın ... kurulumum için parti boyutunu 500'ün üzerine çıkarmak aslında performansı düşürüyordu.

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

3

@Ali Shakiba kodunuzda biraz değişiklik yapılması gerekiyor. Hata bölümü:

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

Güncellenen kod:

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

Bu, tüm cevapta çok daha hızlı ve daha iyi bir yaklaşımdır. Kabul edilen cevap bu olmalı
Arun Shankar

1
Kabul edilen yanıtta belirtildiği gibi, bazı JDBC sürücülerinin / veritabanlarının bir INSERT deyimine dahil edebileceğiniz satır sayısı konusunda sınırları vardır. Yukarıdaki örnekte, myArraybu sınırdan daha büyük bir uzunluğa sahipse , bir istisnaya ulaşırsınız. Benim durumumda, bir toplu işlem yürütme ihtiyacını ortaya çıkaran 1.000 satır sınırım var, çünkü herhangi bir çalışmada 1.000'den fazla satırı potansiyel olarak güncelliyor olabilirim. İzin verilen maksimum değerden daha azını yerleştirdiğinizi biliyorsanız, bu tür bir ifade teorik olarak iyi çalışmalıdır. Akılda tutulması gereken bir şey.
Danny Bullis

Açıklığa kavuşturmak için, yukarıdaki cevap, toplu iş uzunluğu üzerindeki JDBC sürücüsü / veritabanı sınırlamalarından bahseder, ancak benim durumumda gördüğüm gibi, bir insert deyimine dahil edilen satırların sayısında da sınırlamalar olabilir.
Danny Bullis

0

toplu güncellemeleri göndermek için JDBC'de birlikte birden çok güncelleme gönderebiliriz.

autocommit'i devre dışı bırakarak basit güncelleme için Statement, PreparedStatement ve CallableStatement nesnelerini kullanabiliriz

addBatch () ve executeBatch () tüm deyimi BatchUpdate sahip nesneler ile fonksiyonları mevcuttur

burada addBatch () yöntemi, geçerli toplu işleme bir dizi deyim veya parametre ekler.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.