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. Теперь я хочу эффективный способ сохранить отзывы о продукте. Есть два основных случая, которые необходимо рассмотреть для каждого следующего обзора:

  1. Отзыв отсутствует в базе данных -> вставить отзыв со ссылкой на товар, содержащийся в сообщении
  2. Рецензия уже есть в базе данных -> просто добавьте ссылку на продукт в 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);
        }

    }
}

Вопросы:

  1. Иногда я получаю constraintViolationExceptions из-за нарушения уникального ограничения для "reviewIndentifier". Это очевидно, потому что я (одновременно) смотрю, если обзор уже присутствует, а затем вставляю или обновляю его. Как я могу избежать этого?
  2. Лучше использовать save() или saveAndFlush() в моем случае. Я получаю ~50-80 отзывов за секунду. Будет ли сбрасываться hibernate автоматически, если я просто использую save(), или это приведет к значительному увеличению использования памяти?

Обновление до вопроса 1. Будет ли простой @Lock на моем репозитории Review-Prefent исключение-уникальное ограничение?

@Lock(LockModeType.PESSIMISTIC_WRITE)
CustomerReview findByReviewIdentifier(String reviewIdentifier);

Что происходит, когда findByReviewIdentifier возвращает ноль? Может ли hibernate заблокировать reviewIdentifier для потенциальной вставки, даже если метод возвращает значение null?

Спасибо!

1 ответ

С точки зрения производительности, я рассмотрю оценку решения со следующими изменениями.

  1. Переход от двунаправленной ManyToMany к двунаправленной OneToMany

У меня был тот же вопрос, по которому один более эффективен из операторов DML, которые выполняются. Цитирование из типичного сопоставления ManyToMany против двух OneToMany.

Первый вариант может быть проще с точки зрения конфигурации, но он дает менее эффективные операторы DML.

Используйте второй вариант, потому что всякий раз, когда ассоциации управляются ассоциациями @ManyToOne, операторы DML всегда являются наиболее эффективными.


  1. Включить пакетирование операторов DML

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

Цитирование из пакетных операторов INSERT и UPDATE

hibernate.jdbc.batch_size = 50
hibernate.order_inserts = true
hibernate.order_updates = true
hibernate.jdbc.batch_versioned_data = true


  1. Удалить количество вызовов 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 является уникальным и назначается пользователем.

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

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