JPA批量插入(saveAll)

有时候要从第三方导入数据,一般量都比较大,除了方法用异步线程@Async之外,如果每条记录都调用一次save显然对数据库压力很大。可以使用JPA的批量保存方法saveAll(Iterable<S> entities)
由于JPA的批量保存和批量修改是同一个方法,所以本文也适用批量修改操作。

一、Entity改造

增加3个注解,方便在Controller类build方式构造对象。

@Builder
@NoArgsConstructor
@AllArgsConstructor

完整注解:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("休息日,休息日可能不处理业务,备用")
@Entity
@Table(name = "bz_setup_restday", schema = "bankrouter")
public class BzSetupRestdayEntity implements Serializable {

......    

二、Repository

package com.pay.payee.repository;

import com.pay.payee.entity.BzSetupRestdayEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Date;

/**
 * 
休息日,休息日可能不处理业务,备用(BzSetupRestday)表数据库访问层
 *
 * @author 郭秀志 jbcode@126.com
 * @since 2020-05-08 23:50:43
 */
public interface BzSetupRestdayRepository extends JpaRepository<BzSetupRestdayEntity, Date> {

}

三、Service实现类

关键方法saveAll(Iterable<S> entities)

package com.pay.payee.service.impl;

import com.pay.payee.entity.BzSetupRestdayEntity;
import com.pay.payee.repository.BzSetupRestdayRepository;
import com.pay.payee.service.IBzSetupRestdayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * 
休息日,休息日可能不处理业务,备用(BzSetupRestday)表服务实现类
 *
 * @author 郭秀志 jbcode@126.com
 * @since 2020-05-08 23:50:43
 */
@Service("bzSetupRestdayService")
public class BzSetupRestdayServiceImpl implements IBzSetupRestdayService {
    @Autowired
    private BzSetupRestdayRepository bzSetupRestdayRepository;
    
    @Override
    public void save(BzSetupRestdayEntity bzSetupRestdayEntity) {
        bzSetupRestdayRepository.save(bzSetupRestdayEntity);
    }

    public <S extends BzSetupRestdayEntity> List<S> saveAll(Iterable<S> entities) {
        return bzSetupRestdayRepository.saveAll(entities);
    }
}

四、Controller

60000条数据使用方法saveAll(Iterable<S> entities)进行批量保存。

package com.pay.payee.controller;

import com.pay.payee.entity.BzSetupRestdayEntity;
import com.pay.payee.service.IBzSetupRestdayService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 休息日,休息日可能不处理业务,备用(BzSetupRestday)表控制层
 *
 * @author 郭秀志 jbcode@126.com
 * @since 2020-05-08 23:50:43
 */
@RestController
@RequestMapping("/bzSetupRestday")
public class BzSetupRestdayController {
    /**
     * 服务对象
     */
    @Resource
    private IBzSetupRestdayService bzSetupRestdayService;

    /*
     * @Description 批量保存
     * @Param [entities]
     * @return java.util.List<S>
     */
    @GetMapping("/saveAll")
    public <S extends BzSetupRestdayEntity> List<S> saveAll() {
        long begin = System.currentTimeMillis();
        List<BzSetupRestdayEntity> list = new ArrayList<BzSetupRestdayEntity>();
        for (int i = 0; i < 60000; i++) {
            BzSetupRestdayEntity build = BzSetupRestdayEntity.builder().groupId("1").restDate(new Date()).useType("2").build();
            list.add(build);
        }
        List<S> sList = (List<S>) bzSetupRestdayService.saveAll(list);
        long end = System.currentTimeMillis();
        System.out.println("时长:" + (end - begin));
        return sList;
    }
}

五、调用url测试

http://localhost:8555/bzSetupRestday/saveAll
控制台输出耗时(毫秒):时长:15958

网上个别人遇到saveAll速度慢,添加了如下配置信息解决。我实测速度无差别。

spring:
  jpa:
    properties:
      hibernate:
        #打印执行时间统计信息
        generate_statistics: true
        jdbc:
          #每批500条提交
          batch_size: 500
          batch_versioned_data: true
        order_inserts: true
        order_updates: true

六、批量保存优化

6.1 背景

有次实践是有20万左右的数据要批量的插入,速度非常慢,发现打印出来的sql是先select一次,再insert。

6.2 速度慢原因

原生的saveAll()方法可以保证程序的正确性,但是如果数据量比较大效率低,看下源码就知道其原理是 for 循环每一条数据,然后先select一次,如果数据库存在,则update。如果不存在,则insert。

6.3. saveAll源码

SimpleJpaRepositorysaveAll(Iterable<S> entities)方法源码如下:

    @Transactional
    public <S extends T> List<S> saveAll(Iterable<S> entities) {
        Assert.notNull(entities, "Entities must not be null!");
        List<S> result = new ArrayList();
        Iterator var3 = entities.iterator();

        while(var3.hasNext()) {
            S entity = var3.next();
            result.add(this.save(entity));//save方法是核心逻辑
        }

        return result;
    }
    @Transactional
    public <S extends T> S save(S entity) {
        if (this.entityInformation.isNew(entity)) {
            this.em.persist(entity);
            return entity;
        } else {
            return this.em.merge(entity);
        }
    }

6.4 解决方案

6.4.1 批量插入

解决方案是自己用em进行持久化插入,省了一步查询操作。

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addBatch(List<ProjectApplyDO> list) {
        for (ProjectApplyDO projectApplyDO : list) {
            entityManager.persist(projectApplyDO);//insert插入操作
        }
        entityManager.flush();
        entityManager.clear();
    }
6.4.2 批量更新

在确保数据已经存在的情况下,如果是批量更新可以如下代码代替上面的entityManager.persist(projectApplyDO);语句:

entityManager.merge(projectApplyDO);//update更新操作
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270