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

JuH*_*m89 5 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;
}
Run Code Online (Sandbox Code Playgroud)

队列中的一条消息包含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);
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 由于违反了"reviewIndentifier"上的唯一约束,我有时会得到约束的ViolationExceptions.这显然是因为我(同时)查看审核是否已经存在,而不是插入或更新它.我怎么能避免这种情况?
  2. 在我的情况下使用save()或saveAndFlush()是否更好.我每次获得约50-80次评论.如果我只使用save()会自动进行休眠刷新,还是会大大增加内存使用量?

更新问题1:我的Review-Repository上的简单@Lock是否会优先考虑唯一约束异常?

@Lock(LockModeType.PESSIMISTIC_WRITE)
CustomerReview findByReviewIdentifier(String reviewIdentifier);
Run Code Online (Sandbox Code Playgroud)

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

谢谢!

Mad*_*apu 4

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

  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和方法。我相信是唯一的并且是用户指定的。hashCodereviewIdentifierreviewIdentifier

最后,当您通过这些更改进行性能调整时,请使用当前代码作为性能基准。然后进行更改并比较这些更改是否确实为您的解决方案带来了显着的性能改进。