У меня есть приложение весенней загрузки, работающее с спящим режимом. В моей схеме у меня есть город, в котором может быть несколько жителей. У меня есть следующий способ обновления жителей города:
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) спящий режим, необходимо снова получить все объекты.
Я думал о двух подходах к решению этой проблемы, и мне интересно, что лучше (или, может быть, конечно, есть и другие подходы):
@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
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
}
Я буду рад получить некоторое представление о том, какой подход лучше, и, возможно, о других подходах, о которых я не думал. Обратите внимание, что мое приложение потенциально должно обновлять несколько городов одновременно.




Ваше первое решение с одним методом @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);