Массовое/пакетное обновление с использованием Spring Data JPA/Hibernate на Mysql

Я использую Mysql, Spring Data JPA. В моем случае использования у меня есть только 1 таблица, например. Клиент (ID, FIRST_NAME, LAST_NAME). Я пытаюсь добиться пакетного/массового обновления, в котором операторы обновления представляют собой группу, как показано выше в примере для сокращения количества обращений к базе данных.

Я установил все свойства

  • hibernate.order_inserts: правда
  • hibernate.order_updates: правда
  • hibernate.jdbc.batch_versioned_data: правда

Но результат (инструкции обновления не группируются): журналы из общих журналов MySQL

2018-10-28T03:18:32.545233Z 1711 Query update CUSTOMER set FIRST_NAME=’499997′, LAST_NAME=’499998′ where id=499996;
2018-10-28T03:18:32.545488Z 1711 Query update CUSTOMER set FIRST_NAME=’499998′, LAST_NAME=’499999′ where id=499997;
2018-10-28T03:18:32.545809Z 1711 Query update CUSTOMER set FIRST_NAME=’499999′, LAST_NAME=’500000′ where id=499998;

Желаемый результат: (обновления группируются в один запрос, что сокращает количество обращений к БД)

2018-10-28T03:18:32.545233Z 1711 Query update CUSTOMER set FIRST_NAME=’499997′, LAST_NAME=’499998′ where id=499996; update CUSTOMER set FIRST_NAME=’499998′, LAST_NAME=’499999′ where id=499997; update CUSTOMER set FIRST_NAME=’499999′, LAST_NAME=’500000′ where id=499998;

Моему приложению необходимо выполнить более 100 миллионов обновлений, и я полагаю, что это самый быстрый способ.


person Dhruv Bansal    schedule 28.10.2018    source источник
comment
Я верю этому это самое близкое, что вы можете получить с помощью Hibernate/JPA   -  person Yuriy Kravets    schedule 04.07.2019


Ответы (1)


Я предлагаю вам также настроить свойство hibernate.jdbc.batch_size. Ниже приведен небольшой пример, который я пробовал:

int entityCount = 50;
int batchSize = 25;

EntityManager entityManager = entityManagerFactory()
    .createEntityManager();

EntityTransaction entityTransaction = entityManager
    .getTransaction();

try {
    entityTransaction.begin();

    for (int i = 0; i < entityCount; i++) {
        if (i > 0 && i % batchSize == 0) {
            entityTransaction.commit();
            entityTransaction.begin();

            entityManager.clear();
        }

        Post post = new Post(
            String.format("Post %d", i + 1)
        );

        entityManager.persist(post);
    } 

    entityTransaction.commit();
} catch (RuntimeException e) {
    if (entityTransaction.isActive()) {
        entityTransaction.rollback();
    }
    throw e;
} finally {
    entityManager.close();
}

Каждый раз, когда счетчик итераций (например, i) достигает значения, кратного пороговому значению batchSize, мы можем очистить EntityManager и зафиксировать транзакцию базы данных. Фиксируя транзакцию базы данных после каждого пакетного выполнения, мы получаем следующие преимущества:

  • Мы избегаем длительных транзакций, которые вредны для систем реляционных баз данных MVCC.
  • Мы следим за тем, чтобы в случае сбоя мы не потеряли работу, выполненную пакетными заданиями, которые ранее успешно выполнялись.

EntityManager очищается после каждого выполнения пакета, чтобы мы не продолжали накапливать управляемые объекты, которые могут вызвать несколько проблем:

  • Если количество сохраняемых сущностей огромно, мы рискуем исчерпать память.
  • Чем больше сущностей мы накапливаем в Persistence Context, тем медленнее становится сброс. Таким образом, хорошей практикой является сделать контекст сохраняемости как можно более тонким.

Если возникает исключение, мы должны обязательно откатить текущую выполняющуюся транзакцию базы данных. Если этого не сделать, может возникнуть множество проблем, поскольку база данных все еще может считать, что транзакция открыта, и блокировки могут удерживаться до тех пор, пока транзакция не будет завершена по тайм-ауту или администратором баз данных.

В конце нам нужно закрыть EntityManager, чтобы мы могли очистить контекст и освободить ресурсы уровня сеанса.

person paul.anasuya    schedule 28.10.2018