Spring Data JPA - 并发批量插入/更新

时间:2016-04-01 12:33:25

标签: java hibernate spring-data spring-data-jpa

目前我开发了一个Spring Boot应用程序,它主要从消息队列(~5并发消费者)中提取产品评论数据并将它们存储到MySQL DB中。每个评论可以通过其reviewIdentifier(字符串)唯一地标识,该标识符是主键并且可以属于一个或多个产品(例如,具有不同颜色的产品)。以下是数据模型的摘录:

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. 评论不在数据库中 - &gt;插入评论并参考消息中包含的产品
  2. 评论已在数据库中 - &gt;只需将产品参考添加到现有评论的Set productPlacements即可。
  3. 目前我的持久评论方法不是最佳方法。它看起来如下(使用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. 由于违反了“reviewIndentifier”上的唯一约束,我有时会遇到constraintViolationExceptions。这显然是因为我(同时)查看审核是否已经存在,而不是插入或更新它。我怎么能避免这种情况?
    2. 在我的情况下使用save()或saveAndFlush()是否更好。我每次获得约50-80次评论。如果我只使用save()会自动进行休眠刷新,还是会大大增加内存使用量?
    3. 更新问题1:我的Review-Repository上的简单@Lock会不会出现唯一约束异常?

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

      当findByReviewIdentifier返回null时会发生什么?即使方法返回null,hibernate是否可以锁定reviewIdentifier以查找可能的插入?

      谢谢!

1 个答案:

答案 0 :(得分:4)

从性能的角度来看,我会考虑通过以下更改来评估解决方案。

  1. 从双向ManyToMany更改为双向OneToMany
  2. 我对从执行的DML语句中哪一个更有效率有一个相同的问题。引自Typical ManyToMany mapping versus two OneToMany

    从配置角度来看,选项一可能更简单,但它会产生效率较低的DML语句。

      

    使用第二个选项,因为每当关联由@ManyToOne关联控制时,DML语句总是最有效的。

    1. 启用DML语句的批处理
    2. 启用批处理支持将导致数据库往返次数减少,以插入/更新相同数量的记录。

      引自batch INSERT and UPDATE statements

        

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

      1. 删除saveAndFlush调用次数
      2. 当前代码获取ProductPlacement,并且对于每个review,它执行saveAndFlush,这导致不批处理DML语句。

        相反,我会考虑加载ProductPlacement实体并将List<CustomerReview> customerReviews添加到Set<CustomerReview> customerReviews实体的ProductPlacement字段,最后调用merge方法一次最后,有了这两个变化:

        • 制作关联的ProductPlacement实体所有者,即将mappedBy属性移至Set<ProductPlacement> productPlacements实体的CustomerReview字段。
        • 在这些方法中使用CustomerReview字段使equals实体实现hashCodereviewIdentifier方法。我相信reviewIdentifier是唯一且用户已分配。

        最后,当您使用这些更改进行性能调整时,请使用当前代码确定性能基准。然后进行更改并比较更改是否真正导致解决方案的任何显着性能改进。