Java:使用PreparedStatement将多行插入MySQL


87

我想使用Java一次在MySQL表中插入多行。行数是动态的。过去我在做...

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

    myStatement.executeUpdate();
}

我想对此进行优化,以使用MySQL支持的语法:

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

但由于PreparedStatement我不知道有什么方法可以这样做,因为我事先不知道array将包含多少个元素。如果无法使用PreparedStatement,我该怎么办(仍然对数组中的值进行转义)?

Answers:


175

您可以通过创建批处理PreparedStatement#addBatch()并通过执行PreparedStatement#executeBatch()

这是一个启动示例:

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

因为某些JDBC驱动程序和/或DB可能对批处理长度有限制,所以它每1000个项目执行一次。

另请参阅


26
如果将插入的内容放入事务中,则插入的内容将会更快...即包装connection.setAutoCommit(false);connection.commit(); 下载
Joshua Martell 2010年

1
如果有999个项目,看起来您可以执行一个空批处理。
djechlin

2
@electricalbah它将正常执行,因为i == entities.size()
Yohanes AI

这是使用准备好的语句将批处理作业放在一起的另一个很好的资源。viralpatel.net/blogs/batch-insert-in-java-jdbc
Danny Bullis

1
@AndréPaulo:只是适合于已准备好的语句的任何SQL INSERT。有关基本示例,请参考JDBC教程链接。这与具体问题无关。
BalusC

30

使用MySQL驱动程序时,必须将连接参数设置rewriteBatchedStatements为true ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**)

使用此参数,当表仅被锁定一次并且索引仅被更新一次时,该语句将被重写为批量插入。因此它要快得多。

没有此参数,唯一的好处就是源代码更干净。


这是对构造性能的注释:statement.addBatch(); 如果((i + 1)%1000 == 0){statement.executeBatch(); //每执行1000个项目。}
MichalSv 2014年

显然,MySQL驱动程序有一个错误bugs.mysql.com/bug.php?id=71528,这也导致ORM框架(如Hibernate hibernate.atlassian.net/browse/HHH-9134)的问题
Shailendra

是。现在也是正确的。至少对于5.1.45mysql连接器版本。
v.ladynev

<artifactId> mysql-connector-java </ artifactId> <version> 8.0.14 </ version>刚刚检查它是否为8.0.14是正确的。不添加rewriteBatchedStatements=true就不会提高性能。
森特·马修

7

如果可以动态创建sql语句,则可以执行以下解决方法:

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 2010年

这种方法比公认的方法快得多。我测试过,但找不到原因。@JohnS你知道为什么吗?
julian0zzx 2012年

@ julian0zzx不,但是也许因为它是作为单个sql而不是多个sql执行的。但我不确定。
阿里·沙基巴

3

如果您在表中有自动增量并需要访问它,则可以使用以下方法。使用前进行测试,因为Statement中的getGeneratedKeys()取决于所使用的驱动程序。以下代码在Maria DB 10.0.12和Maria JDBC驱动程序1.2上进行了测试

请记住,增加批次大小只会在一定程度上提高性能...因为我的设置将批次大小增加到500以上实际上会降低性能。

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您的代码需要进行一些修改。错误部分:

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

更新的代码:

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

这是整个答案中更快,更好的方法。这应该是公认的答案
Arun Shankar

1
如已接受的答案中所述,某些JDBC驱动程序/数据库对可包含在INSERT语句中的行数有限制。在上面的示例中,如果myArray长度大于该限制,则会遇到异常。就我而言,我有1,000行的限制,这导致需要执行批处理,因为在任何给定的运行中,我都可能更新超过1,000行。如果您知道插入的内容少于允许的最大值,则这种类型的语句理论上应该可以正常工作。要记住的事情。
丹尼·布利斯

为了澄清起见,上面的答案提到了JDBC驱动程序/数据库对批处理长度的限制,但是正如我所看到的那样,插入语句中包含的行数也可能有限制。
丹尼·布利斯

0

我们可以在JDBC中一起提交多个更新,以提交批处理更新。

我们可以使用Statement,PreparedStatement和CallableStatement对象通过禁用自动提交进行bacth更新

addBatch()executeBatch()函数可用于所有语句对象以具有BatchUpdate

在这里,addBatch()方法将一组语句或参数添加到当前批处理中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.