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

Posted

技术标签:

【中文标题】Spring Data JPA - 并发批量插入/更新【英文标题】:Spring Data JPA - concurrent Bulk inserts/updates 【发布时间】:2016-07-21 06:27:00 【问题描述】:

目前我开发了一个 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);
        

    

问题:

    我有时会因为违反“reviewIndentifier”上的唯一约束而得到 constraintViolationExceptions。这显然是因为我(同时)查看评论是否已经存在,然后插入或更新它。我怎样才能避免这种情况? 在我的情况下使用 save() 还是 saveAndFlush() 更好。我每秒收到约 50-80 条评论。如果我只使用 save() 会自动刷新休眠还是会导致内存使用量大大增加?

对问题 1 的更新:我的 Review-Repository 上的简单 @Lock 会防止出现唯一约束异常吗?

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

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

谢谢!

【问题讨论】:

要摆脱竞争条件,要么使saveAllReviews()同步,要么根据审查的键(受约束的属性)实施显式锁定。在我们的组织中,我们也需要处理这种情况。经过 3 年多的尝试和测试,我们找不到比按键锁定更好的方法……也许还有另一种做法,我也想学习它。 感谢您的回复。您是否认为使方法同步和锁定密钥(性能方面)有区别 当然键锁定会更有效,因为您可以安全地允许不同键的并发写入。但这种方法需要实施工作。您可以先尝试synchronized,如果性能不满意,再考虑更高级的技术。 【参考方案1】:

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

    从双向多对多更改为双向单对多

我有一个相同的问题,即执行的 DML 语句中哪个更有效。引用自Typical ManyToMany mapping versus two OneToMany。

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

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


    启用 DML 语句批处理

启用批处理支持将减少到数据库的往返次数以插入/更新相同数量的记录。

引用batch INSERT and UPDATE statements

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


    删除 saveAndFlush 调用次数

当前代码获取ProductPlacement,并且对于每个review,它都会执行saveAndFlush,这会导致没有批处理DML语句。

相反,我会考虑加载ProductPlacement 实体并将List&lt;CustomerReview&gt; customerReviews 添加到ProductPlacement 实体的Set&lt;CustomerReview&gt; customerReviews 字段,最后在最后调用一次merge 方法,并进行以下两个更改:

使 ProductPlacement 实体成为关联的所有者,即将 mappedBy 属性移动到 CustomerReview 实体的 Set&lt;ProductPlacement&gt; productPlacements 字段。 通过在这些方法中使用reviewIdentifier 字段,使CustomerReview 实体实现equalshashCode 方法。我相信reviewIdentifier 是独一无二的,并且是用户分配的。

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

【讨论】:

虽然这一切都提高了性能,但毫无疑问,它如何帮助避免并发查找插入周期的竞争条件? @SashaSalauyou 这是真的。这主要解决问题的性能方面。对于比赛条件,我倾向于同步方法,但我想知道是否有更好的方法,但目前还不确定。 @MadhusudanaReddySunnapu 感谢您的意见。我也想过让 productPlacement 成为关系的所有者,但假设一个产品有 2.5k 评论。这不会导致在集合中添加 10 条评论获得 2.5k 条评论吗?是否可以将项目添加到延迟加载的集合中? @JuHarm89 是的,在这种情况下会导致获取所有评论。怎么样 - 由于 reviewIdentifier 在代码中的某个位置手动分配给新的 Customerreview,我们可以在 Customerreview 中添加 boolean isNew 瞬态字段,该字段将根据是否设置为 true/false是否有新的评论。在上述代码中保存reviews 的同时,我们可以使用HQLnew 执行插入审查并制作映射行。看起来更有效的想法也可以解决 constraintViolationException。不过,一个缺点是 SLC 与 HQL 无效。

以上是关于Spring Data JPA - 并发批量插入/更新的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data JPA HIbernate 批量插入速度较慢

Spring Data JPA saveAll 不进行批量插入

使用 Spring Boot 和 Spring Data JPA 批量插入不起作用

Spring Data JPA:嵌套实体的批量插入

如何为批量插入配置spring boot和data jpa

Spring Data JPA : 批量增删