Java: Chèn nhiều hàng vào MySQL với PreparedStatement


88

Tôi muốn chèn nhiều hàng vào bảng MySQL cùng một lúc bằng Java. Số hàng là động. Trong quá khứ tôi đã làm ...

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

    myStatement.executeUpdate();
}

Tôi muốn tối ưu hóa điều này để sử dụng cú pháp được hỗ trợ bởi MySQL:

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

nhưng với một PreparedStatementtôi không biết có cách nào để làm điều này vì tôi không biết trước arraysẽ chứa bao nhiêu phần tử . Nếu không thể với a PreparedStatement, tôi có thể làm thế nào khác (và vẫn thoát các giá trị trong mảng)?

Câu trả lời:


177

Bạn có thể tạo một lô bằng cách PreparedStatement#addBatch()và thực thi nó bằng PreparedStatement#executeBatch().

Đây là một ví dụ khởi động:

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

Nó được thực thi mỗi 1000 mục vì một số trình điều khiển JDBC và / hoặc DB có thể có giới hạn về độ dài lô.

Xem thêm :


26
Số lần chèn của bạn sẽ nhanh hơn nếu bạn đưa chúng vào giao dịch ... tức là kết thúc bằng connection.setAutoCommit(false);connection.commit(); tải
xuống.oracle.com/javase/tutorial/jdbc/basics/

1
Có vẻ như bạn có thể thực hiện một lô trống nếu có 999 mục.
djechlin

2
@electricalbah nó sẽ thực thi bình thường vìi == entities.size()
Yohanes AI

Đây là một tài nguyên tốt khác về cách kết hợp các công việc hàng loạt với nhau bằng cách sử dụng các câu lệnh đã chuẩn bị. viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis

1
@ AndréPaulo: Chỉ cần CHÈN SQL bất kỳ phù hợp cho một câu lệnh đã soạn sẵn. Tham khảo các liên kết hướng dẫn JDBC để biết các ví dụ cơ bản. Điều này không liên quan đến câu hỏi cụ thể.
BalusC

30

Khi trình điều khiển MySQL được sử dụng, bạn phải đặt tham số kết nối rewriteBatchedStatementsthành true ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

Với thông số này, câu lệnh được viết lại để chèn hàng loạt khi bảng chỉ bị khóa một lần và các chỉ mục chỉ được cập nhật một lần. Vì vậy, nó là nhanh hơn nhiều.

Nếu không có thông số này, lợi thế duy nhất là mã nguồn sạch hơn.


đây là bình luận cho performence cho xây dựng: statement.addBatch (); if ((i + 1)% 1000 == 0) {statement.executeBatch (); // Thực thi mỗi 1000 mục. }
MichalSv

Rõ ràng điều khiển MySQL có một lỗi bugs.mysql.com/bug.php?id=71528 này cũng gây ra vấn đề cho các khuôn khổ ORM như Hibernate hibernate.atlassian.net/browse/HHH-9134
Shailendra

Đúng. Điều này cũng đúng cho bây giờ. Ít nhất là đối với 5.1.45phiên bản trình kết nối mysql.
v.ladynev

<artifactId> mysql-connector-java </artifactId> <version> 8.0.14 </version> Chỉ cần kiểm tra nó là đúng 8.0.14. Không có thêm rewriteBatchedStatements=truethì không có hiệu suất tăng.
vincent mathew

7

Nếu bạn có thể tạo động câu lệnh sql của mình, bạn có thể thực hiện cách giải quyết sau:

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

Tôi tin rằng câu trả lời được chấp nhận là tốt hơn nhiều !! Tôi không biết về cập nhật hàng loạt và khi tôi bắt đầu viết câu trả lời này, câu trả lời đó vẫn chưa được gửi !!! :)
Ali Shakiba

Cách tiếp cận này nhanh hơn nhiều so với cách được chấp nhận. Tôi kiểm tra nó, nhưng không tìm thấy lý do tại sao. @JohnS bạn có biết tại sao không?
julian0zzx 21/11/12

@ julian0zzx không, nhưng có thể do nó được thực thi dưới dạng sql đơn thay vì nhiều. nhưng tôi không chắc.
Ali Shakiba

3

Trong trường hợp bạn có số tăng tự động trong bảng và cần truy cập nó .. bạn có thể sử dụng cách tiếp cận sau ... Hãy kiểm tra trước khi sử dụng vì getGeneratedKeys () trong Statement vì nó phụ thuộc vào trình điều khiển được sử dụng. Đoạn mã dưới đây được thử nghiệm trên Maria DB 10.0.12 và trình điều khiển Maria JDBC 1.2

Hãy nhớ rằng việc tăng kích thước lô chỉ cải thiện hiệu suất ở một mức độ nhất định ... đối với thiết lập của tôi, việc tăng kích thước lô trên 500 đã thực sự làm giảm hiệu suất.

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 mã của bạn cần một số sửa đổi. Phần lỗi:

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

Đã cập nhật mã:

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

Đây là cách tiếp cận nhanh hơn và tốt hơn nhiều trong toàn bộ câu trả lời. Điều này sẽ là câu trả lời được chấp nhận
Arun Shankar

1
Như đã đề cập trong câu trả lời được chấp nhận, một số trình điều khiển / cơ sở dữ liệu JDBC có giới hạn về số hàng mà bạn có thể đưa vào câu lệnh INSERT. Trong trường hợp của ví dụ trên, nếu myArraycó độ dài lớn hơn giới hạn đó, bạn sẽ gặp phải một ngoại lệ. Trong trường hợp của tôi, tôi có giới hạn 1.000 hàng dẫn đến nhu cầu thực thi hàng loạt, vì tôi có thể cập nhật hơn 1.000 hàng trong bất kỳ lần chạy nào. Loại câu lệnh này về mặt lý thuyết sẽ hoạt động tốt nếu bạn biết mình đang chèn ít hơn mức tối đa cho phép. Một cái gì đó để giữ trong tâm trí.
Danny Bullis

Để làm rõ, câu trả lời ở trên đề cập đến giới hạn trình điều khiển / cơ sở dữ liệu JDBC về độ dài lô, nhưng cũng có thể có giới hạn về số hàng được bao gồm trong câu lệnh chèn, như tôi đã thấy trong trường hợp của mình.
Danny Bullis

0

chúng ta có thể gửi nhiều bản cập nhật cùng nhau trong JDBC để gửi các bản cập nhật hàng loạt.

chúng ta có thể sử dụng các đối tượng Statement, PreparedStatement và CallableStatement để cập nhật bacth với tính năng tắt tự động gửi

Các hàm addBatch ()executeBatch () có sẵn với tất cả các đối tượng câu lệnh để có BatchUpdate

ở đây phương thức addBatch () thêm một tập hợp các câu lệnh hoặc tham số vào lô hiện tại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.