事故现场:
数据库我用postgreSQL,持久成框架mybatis
现在有一个操作,需要将一大批数据(3000+)插入数据库,后台直接报错,报错原因如下:
org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 50805
原因:批量插入的顶峰在JDBC-Driver出现天花板.
简单来说就是SQL语句参数上限
可能我们首先想到的解决办法是分段批量插入,将sql语句分成几次执行,比如现在有3500条数据,一次性最多插入1500,分为3次插入;那代码怎么实现呢?
下面介绍2种解决方法,2种方法都用了递归算法;(当时的情况我这边一次性可以插入1800条,具体要看数据表中有多少字段)
- 注:以下方法可以写在service的通用方法中(如果有的话),这样所有实体类的service都可以调用
第一种方法:
这是我的第一次尝试,虽然不太灵活,但能用,记录一下;后来才有第二种方法;
直接用java.util.List包提供的方法subList(int fromIndex, int toIndex);
subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段,可以理解为截取一个集合中的部分元素,他的返回值也是一个List。方法中的2个参数可以看成是数组的下标;代码如下:
/**
* 递归插入
* @author: fangzf
* @param: @param all 需要批量插入的集合
* @param: @param strart 开始的下标
* @param: @param end 结束的下标(包头不包尾)
*/
public void add (List<T> all,int strart, int end){
if(all.size()<=end){
end = all.size();
}
//截取strart ~ end条数据
List<T> collect = all.subList(strart, end);
if(CollUtil.isEmpty(collect)){
return;
}
//批量插入数据的方法(这里是通用的批量插入数据方法)
insertList(collect);
//递归 每次插入1500条数据,这里1500写死了,就是不灵活的地方
//可以再传一个参数过来,这样会比较灵活
add(all,strart+1500,end+1500);
}
//调用批量插入的方法 all表示业务中要插入的数据数组
add(all,0,1500);
关于subList方法需要谨慎使用
在《阿里巴巴Java开发手册》中有提到subList方法需要谨慎使用,具体可以看一下这篇文章:
为什么阿里巴巴要求谨慎使用ArrayList中的subList方法:http://www.matools.com/blog/190706621
第二种方法:(推荐)
说到数组的操作我们肯定会想起java8提供强大的lambda表达式(链接到lambda表示文章),这里要用到skip和limit方法;
-
skip(n):跳过元素,返回一个扔掉前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。
-
limit(n):截断流,使其元素不超过给定数量。
代码如下:
//递归插入
public void add (List<Dormitory> all,long strart,long limit){
//截取 从strart开始截取 截取limit条
List<Dormitory> collect = all.stream().skip(strart).limit(limit).collect(Collectors.toList());
if(CollUtil.isEmpty(collect)){
return;
}
//批量插入数据的方法
insertList(collect);
//递归 每次插入limit条数据
add(all,strart+limit,limit);
}
//调用批量插入的方法 all表示业务中要插入的数据数组
add(all,0L,1500L);
简单强大,推荐