Стратегии извлечения и обновления сущностей с использованием CompletableFuture при весенней загрузке с спящим режимом

У меня есть приложение весенней загрузки, работающее с спящим режимом. В моей схеме у меня есть город, в котором может быть несколько жителей. У меня есть следующий способ обновления жителей города:

public void updateResidentsInCity(long cityId) {
        CompletableFuture
                .supplyAsync(() -> residentsRepository.findAllByCityId(cityId))
                .thenApply(residents -> {
                    // update fields of the resident objects
                    updateResidents(residents);
                    return residents;
                })
                .thenAccept(residents -> residentsRepository.saveAll(residents));
}

Но это вызывает действительно плохие проблемы с производительностью, потому что обновление выполняется в другом потоке, поэтому срок действия сеанса гибернации истек, и когда я вызываю residentsRepository.saveAll(residents) спящий режим, необходимо снова получить все объекты.

Я думал о двух подходах к решению этой проблемы, и мне интересно, что лучше (или, может быть, конечно, есть и другие подходы):

  1. Просто откажитесь от нескольких CompletableFuture(s) — поместите все операции под один @Transactional и сделайте блокирующие вызовы:
@Transactional
public void updateResidentsInCity(long cityId) {
        final List<Resident> residents = residentsRepository.findAllByCityId(cityId);
        updateResidents(residents);
        residentsRepository.saveAll(residents);
}

Теперь я могу просто вызвать updateResidentsInCity() в другой теме:

CompletableFuture.runAsync(() -> updateResidentsInCity(123))

Кроме того, разрешите hibernate выполнять пакетное обновление, добавив эти свойства:

spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_updates=true
  1. сохраните тот же код с CompletableFuture(s), но сделайте residentsRepository.findAllByCityId() транзакцию readOnly и реализуйте мой собственный единственный нативный запрос для пакетного обновления:
public void updateResidentsInCity(long cityId) {
    CompletableFuture
        .supplyAsync(() -> residentsRepository.findAllByCityId(cityId))  // readOnly transaction
        .thenApply(residents -> {
             updateResidents(residents);
             return residents;
         })
         .thenAccept(residents -> residentsRepository.batchUpdate(residents));  // new transaction, one native query
}

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваше первое решение с одним методом @Transactional, вызываемым из асинхронного потока, на самом деле является хорошим решением, поскольку все операции выполняются в одной транзакции. В качестве примечания: вам не нужно вызывать residentsRepository.saveAll(residents) при изменении существующих экземпляров сущностей, полученных Hibernate внутри транзакции. save для новых или «отсоединенных» экземпляров объекта (выбранных ранее, вне текущей транзакции). См.: https://stackoverflow.com/a/46708295

Включение пакетной обработки свойств, как вы это сделали, также очень важно. Размер партии обычно может быть больше. Я использую эти настройки:

spring.jpa.properties.hibernate.jdbc.batch_size=200
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.batch_versioned_data=true

Вы не проявляете логику внутри updateResidents(). Если эта логика достаточно проста, например. увеличить столбец счетчика для каждого или установить некоторое постоянное значение в каждом и т. д., вы можете добиться гораздо большей производительности, используя запрос на обновление в репозитории, либо JPQL, либо собственный. Что-то вроде этого:

@Modifying
@Query("update Resident r set r.someCounter = r.someCounter + 1, r.lastUpdate =:timestamp where r.cityId =:cityId")
void updateResident(@Param("cityId") String cityId, @Param("timestamp") long timestamp);

Другие вопросы по теме

Похожие вопросы

В чем разница между использованием и не использованием <type>pom</type> для зависимости (в разделе <dependencies>)?
Расчет пасхального воскресенья с самой ранней и самой поздней датой
Оптимизация использования ЦП при сопоставлении регулярных выражений Java
Каков эффективный способ нулевой и пустой проверки в установщике
Как я могу объединить две коллекции spring-data-mongodb?
Как вызвать службу SOAP из Spring Boot без сортировки и десортировки с использованием JAXB?
ОШИБКА: «Недостаточно информации для вывода переменной типа T» в Kotlin, как это исправить?
Как передать карту со значениями Stream<String> с помощью Stream API?
Переопределить методы объекта внутри интерфейса, используя методы по умолчанию — ошибка, вводящая в заблуждение
Фатальное исключение: java.lang.NullPointerException — попытка вызвать виртуальный метод 'boolean java.lang.String.equals(java.lang.Object)' для нулевого объекта