有時(shí)候要從第三方導(dǎo)入數(shù)據(jù)隅居,一般量都比較大祖灰,除了方法用異步線程
@Async
之外尿招,如果每條記錄都調(diào)用一次save
顯然對(duì)數(shù)據(jù)庫(kù)壓力很大诀豁±椅遥可以使用JPA的批量保存方法saveAll(Iterable<S> entities)
。
由于JPA的批量保存和批量修改是同一個(gè)方法且叁,所以本文也適用批量修改操作都哭。
一、Entity改造
增加3個(gè)注解逞带,方便在Controller
類build方式構(gòu)造對(duì)象欺矫。
@Builder
@NoArgsConstructor
@AllArgsConstructor
完整注解:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("休息日,休息日可能不處理業(yè)務(wù)展氓,備用")
@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;
/**
*
休息日,休息日可能不處理業(yè)務(wù)遇汞,備用(BzSetupRestday)表數(shù)據(jù)庫(kù)訪問(wèn)層
*
* @author 郭秀志 jbcode@126.com
* @since 2020-05-08 23:50:43
*/
public interface BzSetupRestdayRepository extends JpaRepository<BzSetupRestdayEntity, Date> {
}
三未妹、Service實(shí)現(xiàn)類
關(guān)鍵方法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;
/**
*
休息日空入,休息日可能不處理業(yè)務(wù)络它,備用(BzSetupRestday)表服務(wù)實(shí)現(xiàn)類
*
* @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條數(shù)據(jù)使用方法saveAll(Iterable<S> entities)
進(jìn)行批量保存歪赢。
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;
/**
* 休息日化戳,休息日可能不處理業(yè)務(wù),備用(BzSetupRestday)表控制層
*
* @author 郭秀志 jbcode@126.com
* @since 2020-05-08 23:50:43
*/
@RestController
@RequestMapping("/bzSetupRestday")
public class BzSetupRestdayController {
/**
* 服務(wù)對(duì)象
*/
@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("時(shí)長(zhǎng):" + (end - begin));
return sList;
}
}
五埋凯、調(diào)用url測(cè)試
http://localhost:8555/bzSetupRestday/saveAll
控制臺(tái)輸出耗時(shí)(毫秒):時(shí)長(zhǎng):15958
網(wǎng)上個(gè)別人遇到
saveAll
速度慢点楼,添加了如下配置信息解決。我實(shí)測(cè)速度無(wú)差別白对。
spring:
jpa:
properties:
hibernate:
#打印執(zhí)行時(shí)間統(tǒng)計(jì)信息
generate_statistics: true
jdbc:
#每批500條提交
batch_size: 500
batch_versioned_data: true
order_inserts: true
order_updates: true
六掠廓、批量保存優(yōu)化
6.1 背景
有次實(shí)踐是有20萬(wàn)左右的數(shù)據(jù)要批量的插入,速度非常慢甩恼,發(fā)現(xiàn)打印出來(lái)的sql是先select一次蟀瞧,再insert狰域。
6.2 速度慢原因
原生的saveAll()方法可以保證程序的正確性,但是如果數(shù)據(jù)量比較大效率低黄橘,看下源碼就知道其原理是 for 循環(huán)每一條數(shù)據(jù),然后先select一次屈溉,如果數(shù)據(jù)庫(kù)存在塞关,則update。如果不存在子巾,則insert帆赢。
6.3. saveAll源碼
SimpleJpaRepository
的saveAll(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
進(jìn)行持久化插入,省了一步查詢操作线梗。
@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 批量更新
在確保數(shù)據(jù)已經(jīng)存在的情況下椰于,如果是批量更新可以如下代碼代替上面的entityManager.persist(projectApplyDO);
語(yǔ)句:
entityManager.merge(projectApplyDO);//update更新操作