Spring Data JPA - одновременные массовые вставки / обновления
На данный момент я разрабатываю приложение Spring Boot, которое в основном извлекает данные об обзоре продуктов из очереди сообщений (~5 одновременных потребителей) и сохраняет их в БД MySQL. Каждый отзыв может быть однозначно идентифицирован по его reviewIdentifier (String), который является первичным ключом и может принадлежать одному или нескольким продуктам (например, продуктам разных цветов). Вот выдержка из модели данных:
public class ProductPlacement implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "product_placement_id")
private long id;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="productPlacements")
private Set<CustomerReview> customerReviews;
}
public class CustomerReview implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name = "customer_review_id")
private String reviewIdentifier;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(
name = "tb_miner_review_to_product",
joinColumns = @JoinColumn(name = "customer_review_id"),
inverseJoinColumns = @JoinColumn(name = "product_placement_id")
)
private Set<ProductPlacement> productPlacements;
}
Одно сообщение из очереди содержит 1 - 15 отзывов и productPlacementId. Теперь я хочу эффективный способ сохранить отзывы о продукте. Есть два основных случая, которые необходимо рассмотреть для каждого следующего обзора:
- Отзыв отсутствует в базе данных -> вставить отзыв со ссылкой на товар, содержащийся в сообщении
- Рецензия уже есть в базе данных -> просто добавьте ссылку на продукт в Set productPlacements существующей рецензии.
В настоящее время мой метод сохранения отзывов не является оптимальным. Это выглядит следующим образом (использует Spring Data JpaRespoitories):
@Override
@Transactional
public void saveAllReviews(List<CustomerReview> customerReviews, long productPlacementId) {
ProductPlacement placement = productPlacementRepository.findOne(productPlacementId);
for(CustomerReview review: customerReviews){
CustomerReview cr = customerReviewRepository.findOne(review.getReviewIdentifier());
if (cr!=null){
cr.getProductPlacements().add(placement);
customerReviewRepository.saveAndFlush(cr);
}
else{
Set<ProductPlacement> productPlacements = new HashSet<>();
productPlacements.add(placement);
review.setProductPlacements(productPlacements);
cr = review;
customerReviewRepository.saveAndFlush(cr);
}
}
}
Вопросы:
- Иногда я получаю constraintViolationExceptions из-за нарушения уникального ограничения для "reviewIndentifier". Это очевидно, потому что я (одновременно) смотрю, если обзор уже присутствует, а затем вставляю или обновляю его. Как я могу избежать этого?
- Лучше использовать save() или saveAndFlush() в моем случае. Я получаю ~50-80 отзывов за секунду. Будет ли сбрасываться hibernate автоматически, если я просто использую save(), или это приведет к значительному увеличению использования памяти?
Обновление до вопроса 1. Будет ли простой @Lock на моем репозитории Review-Prefent исключение-уникальное ограничение?
@Lock(LockModeType.PESSIMISTIC_WRITE)
CustomerReview findByReviewIdentifier(String reviewIdentifier);
Что происходит, когда findByReviewIdentifier возвращает ноль? Может ли hibernate заблокировать reviewIdentifier для потенциальной вставки, даже если метод возвращает значение null?
Спасибо!
1 ответ
С точки зрения производительности, я рассмотрю оценку решения со следующими изменениями.
- Переход от двунаправленной ManyToMany к двунаправленной OneToMany
У меня был тот же вопрос, по которому один более эффективен из операторов DML, которые выполняются. Цитирование из типичного сопоставления ManyToMany против двух OneToMany.
Первый вариант может быть проще с точки зрения конфигурации, но он дает менее эффективные операторы DML.
Используйте второй вариант, потому что всякий раз, когда ассоциации управляются ассоциациями @ManyToOne, операторы DML всегда являются наиболее эффективными.
- Включить пакетирование операторов DML
Включение поддержки пакетной обработки приведет к уменьшению количества обращений к базе данных для вставки / обновления одинакового количества записей.
Цитирование из пакетных операторов INSERT и UPDATE
hibernate.jdbc.batch_size = 50
hibernate.order_inserts = true
hibernate.order_updates = true
hibernate.jdbc.batch_versioned_data = true
- Удалить количество вызовов saveAndFlush
Текущий код получает ProductPlacement
и для каждого review
это делает saveAndFlush
, что не приводит к пакетированию операторов DML.
Вместо этого я хотел бы рассмотреть возможность загрузки ProductPlacement
сущность и добавление List<CustomerReview> customerReviews
к Set<CustomerReview> customerReviews
поле ProductPlacement
сущность и, наконец, назвать merge
метод один раз в конце, с этими двумя изменениями:
- Изготовление
ProductPlacement
владелец юридического лица ассоциации, т. е. путем перемещенияmappedBy
приписатьSet<ProductPlacement> productPlacements
полеCustomerReview
юридическое лицо. - Изготовление
CustomerReview
субъект реализацииequals
а такжеhashCode
метод с помощьюreviewIdentifier
поле в этих методах. я верюreviewIdentifier
является уникальным и назначается пользователем.
Наконец, по мере того, как вы будете выполнять настройку производительности с этими изменениями, оцените свою производительность в соответствии с текущим кодом. Затем внесите изменения и сравните, действительно ли эти изменения приводят к значительному повышению производительности вашего решения.