美文网首页程序员DB
JPA批量插入(saveAll)

JPA批量插入(saveAll)

作者: 梅西爱骑车 | 来源:发表于2020-05-09 01:07 被阅读0次

    有时候要从第三方导入数据,一般量都比较大,除了方法用异步线程@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更新操作
    

    相关文章

      网友评论

        本文标题:JPA批量插入(saveAll)

        本文链接:https://www.haomeiwen.com/subject/jdgcnhtx.html