春季JPA批量更新很慢1,000个实体耗时20秒 @Entity类 Id类请求数据示例数据库数据示例解决方案1:带批处理的SaveAll解决方案2:Mysql本机查询在重复键更新时插入〜

问题描述

当我尝试添加测试数据(1,000个实体)时,花费了 1m 5s。

所以我读了很多文章,然后将处理时间减少到 20秒

但是这对我来说仍然很慢,我相信比我使用的方法还有更多好的解决方案。是否有人有良好的习惯来处理?

我还想知道哪一部分会使速度变慢?

  1. 持久性上下文
  2. 其他选择

谢谢!


@Entity类

此实体类用于从用户的手机收集到用户的健康数据的步行步骤。

PK为userIdrecorded_at(PK中的recorded_at来自请求数据)

@Getter
@NoArgsConstructor
@IdClass(StepId.class)
@Entity
public class StepRecord {
    @Id
    @ManyToOne(targetEntity = User.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id",referencedColumnName = "id",insertable = false,updatable = false)
    private User user;

    @Id
    private ZonedDateTime recordedAt;

    @Column
    private Long count;

    @Builder
    public StepRecord(User user,ZonedDateTime recordedAt,Long count) {
        this.user = user;
        this.recordedAt = recordedAt;
        this.count = count;
    }
}

Id类

Id class(here)中的

用户字段,它是 UUID类型In Entity class,用户是用户实体类型。没关系,这会有问题吗?

@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class StepId implements Serializable {
    @Type(type = "uuid-char")
    private UUID user;
    private ZonedDateTime recordedAt;
}

请求数据示例

// I'll get user_id from logined user
// user_id(UUID) like 'a167d363-bfa4-48ae-8d7b-2f6fc84337f0'

[{
    "count": 356,"recorded_at": "2020-09-16T04:02:34.822Z"
},{
    "count": 3912,"recorded_at": "2020-09-16T08:02:34.822Z"
},{
    "count": 8912,"recorded_at": "2020-09-16T11:02:34.822Z"
},{
    "count": 9004,"recorded_at": "2020-09-16T11:02:34.822Z" // <-- if duplicated,update
}
]

数据库数据示例


|user_id (same user here)            |recorded_at        |count|
|------------------------------------|-------------------|-----|
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 04:02:34|356  | <-insert
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 08:21:34|3912 | <-insert
|a167d363-bfa4-48ae-8d7b-2f6fc84337f0|2020-09-16 11:02:34|9004 | <-update


解决方案1:带批处理的SaveAll()

  1. application.properties
spring:
  jpa:
    properties:
      hibernate:
        jdbc.batch_size: 20
        jdbc.batch_versioned_data: true
        order_inserts: true
        order_updates: true
        generate_statistics: true
  1. 服务
public void saveBatch(User user,List<StepRecordDto.SaveRequest> requestList) {
        List<StepRecord> chunk = new ArrayList<>();

        for (int i = 0; i < requestList.size(); i++) {
            chunk.add(requestList.get(i).toEntity(user));

            if ( ((i + 1) % BATCH_SIZE) == 0 && i > 0) {
                repository.saveAll(chunk);
                chunk.clear();
                //entityManager.flush(); // doesn't help
                //entityManager.clear(); // doesn't help 
            }
        }

        if (chunk.size() > 0) {
            repository.saveAll(chunk);
            chunk.clear();
        }
    }

我读了一篇文章,内容是如果我在Entity类中添加“ @Version”字段,但仍然需要额外选择。几乎花了相同的时间(20s)。

链接在这里⇒https://persistencelayer.wixsite.com/springboot-hibernate/post/the-best-way-to-batch-inserts-via-saveall-iterable-s-entities

但这对我没有帮助。我想我将PK密钥与数据一起传递,所以它总是调用merge()。

(如果我误解了@Version,请告诉我)


解决方案2:Mysql本机查询(在重复键更新时插入〜)

我猜测mysql本机查询中的Insert into ~ on duplicate key update ~可能比merge() <- select/insert

mysql本机查询也可以选择检查重复密钥,但是我认为mysql引擎已经过优化。

  1. 存储库
public interface StepRecordRepository extends JpaRepository<StepRecord,Long> {
    @Query(value = "insert into step_record(user_id,recorded_at,count) values (:user_id,:recorded_at,:count) on duplicate key update count = :count",nativeQuery = true)
    void upsertNative(@Param("user_id") String userId,@Param("recorded_at") ZonedDateTime recorded_at,@Param("count") Long count);
}
  1. 服务
public void saveNative(User user,List<StepRecordDto.SaveRequest> requestList) {
        requestList.forEach(x ->
                repository.upsertNative(user.getId().toString(),x.getRecordedAt(),x.getCount()));
    }

两种方法都花了20秒才能处理1000个实体。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)