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')[, ...]

але PreparedStatementя не знаю, як це зробити, оскільки я не знаю заздалегідь, скільки елементів arrayбуде містити. Якщо це неможливо за допомогою a 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/tutorial/jdbc/basics/

1
Схоже, ви можете виконати порожню партію, якщо є 999 елементів.
djechlin 07.03.13

2
@electricalbah він буде виконуватися нормально, тому щоi == entities.size()
Йоханес А.І.

Ось ще один хороший ресурс щодо поєднання пакетних завдань за допомогою підготовлених виписок. viralpatel.net/blogs/batch-insert-in-java-jdbc
Денні Булліс

1
@ AndréPaulo: Будь-який SQL INSERT, придатний для підготовленої заяви. Основні приклади див. У підручнику з JDBC. Це не пов'язано з конкретним питанням.
BalusC

30

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

За допомогою цього параметра оператор переписується на групове вставлення, коли таблиця блокується лише один раз, а індекси оновлюються лише один раз. Так це набагато швидше.

Без цього параметра єдиною перевагою є чистіший вихідний код.


це коментар для виконання для побудови: statement.addBatch (); if ((i + 1)% 1000 == 0) {statement.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не буде вищої продуктивності.
Вінсент Метью

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

Я вважаю, що прийнята відповідь набагато краща !! Я не знав про пакетні оновлення, і коли я почав писати цю відповідь, ця відповідь ще не надіслана !!! :)
Алі Шакіба

Такий підхід набагато швидший за прийнятий. Я тестую, але не знаходжу, чому. @JohnS ти знаєш чому?
julian0zzx

@ julian0zzx ні, але, можливо, тому що він виконується як один sql, а не як множинний. але я не впевнений.
Алі Шакіба

3

Якщо у вас є автоматичне збільшення в таблиці і вам потрібно отримати до неї доступ, ви можете скористатися наступним підходом ... Виконайте тест перед використанням, оскільки 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();

Це набагато швидший та кращий підхід у всій відповіді. Це має бути прийнятою відповіддю
Арун Шанкар

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.