前言:
最近有需求,做一个web端的Excel数据导入的功能,涉及到百万级数据体量的导入,1)excel实现可供选择的是poi和easyexcel实现(因为项目中已依赖poi的低版本依赖,引入easyexcel会提示依赖包冲突,最终选择poi实现,本人后续会记录具体功能实现代码),2)数据库处理及存入上因为涉及到大量数据,本人使多线程方法执行,在存入数据库过程,本人先使用的还是mybatis-plus实现,实现效果比较差:不到百万的数据,导入时间超过半小时,最后采用jdbc的批量导入来实现的该功能,线程连接池使用c3p0实现,具体项目demo后续提供。
其中:
多线程上未使用线程池,代码如下:
引入import java.util.concurrent.CountDownLatch;
private CountDownLatch countDownLatch;
long start = Calendar.getInstance().getTimeInMillis();//开始时间,单位是毫秒
// 在这里 list 需要复制 需要插入数据库的实例集合
// 异步多线程 插入数据库
int total = list.size();
//每40000条数据,调用一次线程(具体数量配置看服务器或本地cpu及性能调整)
int batchSize = 40000;
int number =total%batchSize ==0 ?total / batchSize :total / batchSize+1;
countDownLatch = new CountDownLatch(number);
for(int i = 0;i<number;i++){
List<Map<String, Object>> batchList = new ArrayList<>();
if(i== number-1){ // 最后一个
batchList = list.subList(i*batchSize,total);
}else{
batchList = list.subList(i*batchSize,(i+1)*batchSize);
}
/*创建多线程*/
logger.info("启动多线程:");
this.asyncBatchAddXml(countDownLatch,batchList);
}
}
long end = Calendar.getInstance().getTimeInMillis();//结束时间
double spentTime = (double) end - end1; //末减初就是所花时间
logger.info("===数据导入时间:"+spentTime+"共计:"+list.size()+"条成功数据");
/*更新导入文件表内容*/
list为 List<Map<String, Object>> list = new ArrayList<>();是导入的全量数据
@Async("asyncServiceExecutor")
public Integer asyncBatchAddXml(CountDownLatch countDownLatch, List<Map<String, Object>> batchList) {
try {
int count = insertAll("plugging_url", batchList);
if(count>0){
return Integer.valueOf(count);
}else{
return 0;
}
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
return 0;
}finally {
countDownLatch.countDown();
}
}
asyncBatchAddXml为多线程调用方法
/**
* 执行数据库插入操作 4 * @param datas 插入数据表中key为列名和value为列对应的值的Map对象的List集合
* @param tableName 要插入的数据库的表名
* @return 影响的行数
* @throws SQLException SQL异常
*/
public int insertAll(String tableName, List<Map<String, Object>> datas) throws SQLException {
/**影响的行数**/
int affectRowCount = -1;
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
/**从数据库连接池中获取数据库连接**/
connection = dataSource.getConnection();
Map<String, Object> valueMap = datas.get(0);
/**获取数据库插入的Map的键值对的值**/
Set<String> keySet = valueMap.keySet();
Iterator<String> iterator = keySet.iterator();
/**要插入的字段sql,其实就是用key拼起来的**/
StringBuilder columnSql = new StringBuilder();
/**要插入的字段值,其实就是?**/
StringBuilder unknownMarkSql = new StringBuilder();
Object[] keys = new Object[valueMap.size()];
int i = 0;
while (iterator.hasNext()) {
String key = iterator.next();
keys[i] = key;
columnSql.append(i == 0 ? "" : ",");
columnSql.append(key);
unknownMarkSql.append(i == 0 ? "" : ",");
/*时间格式需要转换*/
if(key.contains("time")||key.contains("TIME")){
unknownMarkSql.append("to_date(?,'yyyy-mm-dd hh24:MI')");
}else{
unknownMarkSql.append("?");
}
i++;
}
/**开始拼插入的sql语句**/
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ");
sql.append(tableName);
sql.append(" (");
sql.append(columnSql);
sql.append(" ) VALUES (");
sql.append(unknownMarkSql);
sql.append(" )");
/**执行SQL预编译**/
preparedStatement = connection.prepareStatement(sql.toString());
/**设置不自动提交,以便于在出现异常的时候数据库回滚**/
connection.setAutoCommit(false);
System.out.println(sql.toString());
for (int j = 0; j < datas.size(); j++) {
for (int k = 0; k < keys.length; k++) {
preparedStatement.setObject(k + 1, datas.get(j).get(keys[k]));
}
preparedStatement.addBatch();
}
int[] arr = preparedStatement.executeBatch();
connection.commit();
affectRowCount = arr.length;
System.out.println("JDBC成功了插入了"+affectRowCount+"行");
} catch (Exception e) {
if (connection != null) {
connection.rollback();
}
e.printStackTrace();
throw e;
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
}
return affectRowCount;
}
insertAll为JDBC批量导入方法,其中数据库使用c3p0线程池连接
效果实际:
image.png
300W+的数据,文件大小在60mb,文件上传时间3min,处理时间2.3min
网友评论