前台数据批量导入到数据库

说明

用到的框架

RuoYi-Vue: 🎉 基于 SpringBoot,Spring Security,JWT,Vue & Element 的前后端分离权限管理系统,同时提供了 Vue3 的版本
https://gitee.com/y_project/RuoYi-Vue

关键 dependency

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JSON 工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 阿里 easy Excel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.3</version>
</dependency>
<!-- 分页插件依赖了 mybatis-spring-boot -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

通过 excel 进行导入

注:以下均为为简要说明,完整示例请获取完整代码。

com.ruoyi.prevention.controller.PopulationController.java

1
2
3
4
5
6
7
8
9
@PostMapping("/importData")
public R<String> importData(@RequestParam(value="file") MultipartFile file) throws Exception {
if (file == null) {
logger.info("file is null");
}
final PopulationExcelReadListener readListener = new PopulationExcelReadListener(populationMapper);
EasyExcel.read(file.getInputStream(), DailyNucleicInputExcel.class, readListener).doReadAll();
return R.ok("已导入" + readListener.getEffectRows() + "条记录");
}

com.ruoyi.prevention.utils.PopulationExcelReadListener 进行数据的校验和获取数据并入库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class PopulationExcelReadListener implements ReadListener<DailyNucleicInputExcel> {

/**
* 每隔 500 条存储数据库,然后清理 list,方便内存回收
*/
private static final int BATCH_COUNT = 500;
/**
* 常住人口
*/
private final List<PopulationDO> cachedResidentDataList = new ArrayList<>(BATCH_COUNT);

/**
* 假设这个是一个 DAO,当然有业务逻辑这个也可以是一个 service。当然如果不用存储这个对象没用。
*/
private final PopulationMapper mapper;

/**
* 如果使用了 spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param mapper
*/
public PopulationExcelReadListener(PopulationMapper mapper) {
this.mapper = mapper;
}

@Override
public void invoke(DailyNucleicInputExcel excelData, AnalysisContext context) {
// 数据校验 + 过滤条件
// 拿到单个对象并加入 list
// 判断如果 list 已满则批量插入
}

@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
}
}

com.ruoyi.prevention.mapper.PopulationMapper 接口声明

1
2
3
4
5
6
7
/**
* 批量插入常住人员
*
* @param list 设备
* @return 结果
*/
long insertResidentBatch(List<PopulationDO> list);

src\main\resources\mapper\prevention\PopulationMapper.xml

这里选择 foreach 批量插入效率高。目前从接口测试 50 万条数据耗时 100 秒上下。另外可按实际需要是否搭配 ON DUPLICATE KEY UPDATE 进行覆盖插入。另外请根据需要是否要启用多线程进行插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 <!-- 批量插入-->
<insert id="insertResidentBatch" parameterType="java.util.List">
INSERT IGNORE INTO population
(card_number, name, tel, district
, town, community, addr, detail_addr, update_time
) VALUES
<foreach collection="list" item="it" separator=",">
(
#{it.cardNumber}, #{it.name}, #{it.tel}, #{it.district}
, #{it.town}, #{it.community}, #{it.addr}, #{it.detailAddr}, #{it.updateTime}
)
</foreach>
ON DUPLICATE KEY UPDATE

name = values(name),
tel = values(tel),
district = values(district),

town = values(town),
community = values(community),
addr = values(addr),
detail_addr = values(detail_addr),
update_time = values(update_time)
</insert>

通过 json 进行导入

com.ruoyi.prevention.controller.PopulationController.java

和通过 excel 进行类型,直接将前端传入的 json 数据进行校验去重过滤等操作后直接存入数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@PostMapping("/importDataByJson")
public R<String> insertBatch(@RequestBody final DailyNucleicInputExcel[] array) {
logger.info("importDataByJson ---: 导入 json 人员信息条目数 = {}", array.length);
final Date timeStampDate = new Date();
// 姓名不为空 且 不包含服务区 且 身份证去重
final List<PopulationDO> dailyNucleicDOList = Arrays.stream(array)
.filter(DailyNucleicInputExcel::nameIsNotNull)
.filter(DailyNucleicInputExcel::cardNumberIsValid)
.filter(DailyNucleicInputExcel::notContainsServiceArea)
.distinct()
.map(it -> PopulationFactory.newInstance(it, timeStampDate))
.collect(Collectors.toList());
final long effectRows = this.populationService.insertResidentBatch(dailyNucleicDOList);
return R.ok("已导入" + array.length + "条记录");
}

com.ruoyi.prevention.service.PopulationService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public long insertResidentBatch(List<PopulationDO> populationDOList) {
final int size = populationDOList.size();
final int batchSize = 500;
int used = 0;
long inserts = 0;
while (used < size) {
int step = Math.min(size - used, batchSize);
long before = System.currentTimeMillis();
inserts += this.populationMapper.insertResidentBatch(populationDOList.subList(used, used + step));
this.logger.debug("插入耗时:" + (System.currentTimeMillis() - before) + "毫秒");
used += step;
}
return inserts;
}

导出人员信息到 excel

1
2
3
4
5
6
7
@PostMapping("/export")
public void export(HttpServletResponse response, @RequestBody PopulationListInputVO inputVO)
throws UnsupportedEncodingException {
// 从数据库条件查询要导出的数据后进行导出即可 ...
final EasyExcelUtil<PopulationOutputVO> easyExcelUtil = new EasyExcelUtil<>(PopulationOutputVO.class);
easyExcelUtil.exportEasyExcel(response, populationQueryList, this.sheetName);
}