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


88

Я хочу вставить сразу несколько строк в таблицу 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')[, ...]

но с a PreparedStatementя не знаю, как это сделать, так как заранее не знаю, сколько элементов arrayбудет содержать. Если это невозможно с помощью PreparedStatement, как еще я могу это сделать (и при этом избежать значений в массиве)?

Ответы:


177

Вы можете создать пакет 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 и / или базы данных могут иметь ограничение на длину пакета.

Также :


26
Ваши вставки будут работать быстрее, если вы поместите их в транзакции ... то есть оберните connection.setAutoCommit(false);и connection.commit(); загрузите.oracle.com
javase/

1
Похоже, вы можете выполнить пустой пакет, если есть 999 элементов.
djechlin 07

2
@electricalbah, он будет работать нормально, потому чтоi == entities.size()
Yohanes AI

Вот еще один хороший ресурс по объединению пакетных заданий с использованием подготовленных операторов. viralpatel.net/blogs/batch-insert-in-java-jdbc
Дэнни

1
@ AndréPaulo: Любой SQL INSERT, подходящий для подготовленного оператора. Обратитесь к ссылкам на учебник JDBC за основными примерами. Это не связано с конкретным вопросом.
BalusC 04

31

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

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

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


это комментарий для производительности конструкции: statement.addBatch (); если ((я + 1)% 1000 == 0) {оператор.executeBatch (); // Выполнять каждые 1000 элементов. }
MichalSv

По-видимому, в драйвере MySQL есть ошибка bugs.mysql.com/bug.php?id=71528 Это также вызывает проблемы для фреймворков ORM, таких как Hibernate hibernate.atlassian.net/browse/HHH-9134
Шайлендра

Да. Это верно и сейчас. По крайней мере, для 5.1.45версии разъема mysql.
v.ladynev

<artifactId> mysql-connector-java </artifactId> <version> 8.0.14 </version> Только что проверил, что версия 8.0.14 верна. Без добавления rewriteBatchedStatements=trueнет прироста производительности.
Винсент Мэтью

8

Если вы можете создать свой оператор 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();

Я считаю, что принятый ответ намного лучше !! Я не знал о пакетных обновлениях, и когда я начал писать этот ответ, этот ответ еще не был отправлен !!! :)
Али Шакиба

Этот подход намного быстрее, чем принятый. Я тестирую, но не понимаю почему. @JohnS ты знаешь почему?
julian0zzx

@ julian0zzx нет, но, возможно, потому, что он выполняется как один sql, а не как несколько. но я не уверен.
Али Шакиба

3

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

Это гораздо более быстрый и лучший подход в целом. Это должен быть принятый ответ
Арун Шанкар,

1
Как упоминалось в принятом ответе, некоторые драйверы / базы данных JDBC имеют ограничения на количество строк, которые вы можете включить в оператор INSERT. В случае приведенного выше примера, если myArrayдлина превышает этот предел, вы столкнетесь с исключением. В моем случае у меня есть ограничение в 1000 строк, что вызывает необходимость пакетного выполнения, потому что я потенциально могу обновлять более 1000 строк при любом заданном запуске. Этот тип оператора теоретически должен работать нормально, если вы знаете, что вставляете меньше максимально допустимого. Что нужно иметь в виду.
Дэнни Буллис

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

0

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.