Java: вставьте несколько строк в MySQL с помощью PreparedStatement

Я хочу вставить несколько строк в таблицу MySQL одновременно, используя Java. Количество строк является динамическим. Раньше я занимался...

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, как еще я могу это сделать (и при этом экранировать значения в массиве)?


person Tom Marthenal    schedule 04.12.2010    source источник


Ответы (6)


Вы можете создать пакет с помощью 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.
            }
        }
    }
}

Он выполняется каждые 1000 элементов, поскольку некоторые драйверы JDBC и/или базы данных могут иметь ограничение на длину пакета.

См. также:

person BalusC    schedule 04.12.2010
comment
Ваши вставки будут выполняться быстрее, если вы поместите их в транзакции... т.е. оберните их connection.setAutoCommit(false); и connection.commit(); download.oracle.com/javase/tutorial/jdbc/basics/ - person Joshua Martell; 04.12.2010
comment
Похоже, вы можете выполнить пустую партию, если есть 999 элементов. - person djechlin; 08.03.2013
comment
@electricalbah будет выполняться нормально, потому что i == entities.size() - person Yohanes AI; 31.03.2017
comment
Вот еще один хороший ресурс по объединению пакетных заданий с помощью подготовленных операторов. viralpatel.net/blogs/batch-insert-in-java-jdbc - person Danny Bullis; 03.05.2018
comment
Как в этом случае выглядит SQL_INSERT? Что-то вроде этого INSERT INTO MyTable (value1, value2) VALUES (?,?)? - person André; 04.06.2018
comment
@AndréPaulo: любой SQL INSERT, подходящий для подготовленного оператора. Обратитесь к ссылкам на учебник JDBC для основных примеров. Это не относится к конкретному вопросу. - person BalusC; 04.06.2018

Когда используется драйвер MySQL, вы должны установить для параметра соединения rewriteBatchedStatements значение true ( jdbc:mysql://localhost:3306/TestDB?**rewriteBatchedStatements=true**).

С этим параметром оператор переписывается на массовую вставку, когда таблица блокируется только один раз и индексы обновляются только один раз. Так намного быстрее.

Без этого параметра единственным преимуществом является более чистый исходный код.

person MichalSv    schedule 29.05.2014
comment
это комментарий для производительности при построении: statement.addBatch(); если ((i + 1) % 1000 == 0) { statement.executeBatch(); // Выполнять каждые 1000 элементов. } - person MichalSv; 29.05.2014
comment
Очевидно, в драйвере MySQL есть ошибка bugs.mysql.com/bug.php?id=71528 Это также вызывает проблемы для фреймворков ORM, таких как Hibernate hibernate.atlassian.net/browse/HHH- 9134 - person Shailendra; 31.05.2014
comment
да. Это правильно и сейчас. По крайней мере, для 5.1.45 версии коннектора mysql. - person v.ladynev; 28.02.2018
comment
‹artifactId›mysql-connector-java‹/artifactId› ‹version›8.0.14‹/version› Только что проверил, что это правильно для 8.0.14. Без добавления rewriteBatchedStatements=true нет прироста производительности. - person vincent mathew; 18.05.2019

Если вы можете создать свой оператор 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();
person Ali Shakiba    schedule 04.12.2010
comment
Я считаю, что принятый ответ намного лучше! Я не знал о пакетных обновлениях, и когда я начал писать этот ответ, этот ответ еще не был отправлен !!! :) - person Ali Shakiba; 05.12.2010
comment
Этот подход намного быстрее общепринятого. Я проверяю это, но не нахожу, почему. @JohnS ты знаешь почему? - person julian0zzx; 21.11.2012
comment
@ julian0zzx нет, но, возможно, потому, что он выполняется как один sql, а не как несколько. но я не уверен. - person Ali Shakiba; 23.11.2012

Если у вас есть автоматическое увеличение в таблице и вам нужно получить к ней доступ... вы можете использовать следующий подход... Проведите тест перед использованием, потому что 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();
    }
}
person gladiator    schedule 30.07.2015

@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();
person vinay    schedule 18.10.2017
comment
Это гораздо более быстрый и лучший подход во всем ответе. Это должен быть принятый ответ - person Arun Shankar; 09.11.2017
comment
Как упоминалось в принятом ответе, некоторые драйверы/базы данных JDBC имеют ограничения на количество строк, которые вы можете включить в инструкцию INSERT. В случае приведенного выше примера, если myArray имеет большую длину, чем это ограничение, вы столкнетесь с исключением. В моем случае у меня есть ограничение в 1000 строк, что вызывает необходимость пакетного выполнения, потому что я потенциально могу обновить более 1000 строк при любом заданном запуске. Этот тип оператора теоретически должен работать нормально, если вы знаете, что вставляете меньше максимально допустимого. Что-то, что нужно иметь в виду. - person Danny Bullis; 03.05.2018
comment
Чтобы уточнить, в приведенном выше ответе упоминаются ограничения драйвера/базы данных JDBC на длину пакета, но также могут быть ограничения на количество строк, включенных в оператор вставки, как я видел в моем случае. - person Danny Bullis; 03.05.2018

мы можем отправлять несколько обновлений вместе в JDBC для отправки пакетных обновлений.

мы можем использовать объекты Statement, PreparedStatement и CallableStatement для обновления bacth с отключением автофиксации.

Функции addBatch() и executeBatch() доступны для всех объектов операторов, для которых установлено BatchUpdate.

здесь метод addBatch() добавляет набор операторов или параметров к текущему пакету.

person kapil das    schedule 15.08.2013