数据说明:第一条数据完整,可以成功导入;第二条数据无姓名,业务逻辑姓名不允许为空,会导出到错误Excel中;第三条数据无姓名无类型,业务逻辑姓名类型不能为空,会导出错误Excel中。
选择相关Excel,点击导出测试按钮:
这块主要有两个关于Excel的工具类,一个将Excel转化成List集合的工具类,它的名字叫ExcelToEntityListUtil.java;另一个是将List集合转化为Excel的工具类,它的名字叫EntityListToExcelUtil.java。它两有一个共同的特点是:都是单例类,而且我在这里采用的静态内部类的单例,这样做的好处是既可以懒加载,又保证了线程安全,效率也比较高。这种静态内部类的单例模式比较推荐大家去使用。相关代码如下:
ExcelToEntityListUtil.java:
package com.hanxiaozhang.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
* 功能描述:
* 〈Excel导入成实体集合〉
*
* @Author:hanxinghua
* @Date: 2020/2/24
*/
@Slf4j
public class ExcelToEntityListUtil {
private BeanStorage storage = new BeanStorage();
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private ExcelToEntityListUtil(){}
private static class Holder{
private static ExcelToEntityListUtil INSTANCE=new ExcelToEntityListUtil();
}
public static ExcelToEntityListUtil getInstance(){
return Holder.INSTANCE;
}
/**
* 执行Excel转EntityList
*
* @param entity
* @param excel excel输入流
* @param titleToAttr key为excel的中文title,value为该中文title对于的entity属性名
* @param
* @return
* @throws IOException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvalidFormatException
*/
public ArrayList execute(Class> entity, InputStream excel, Map titleToAttr) throws IOException, InstantiationException, IllegalAccessException, IllegalArgumentException,InvalidFormatException {
ArrayList result = new ArrayList();
Workbook book = create(excel);
Sheet sheet = book.getSheetAt(0);
int rowCount = sheet.getLastRowNum();
if (rowCount < 1) {
return result;
}
//加载标题栏数据,以此和headMapping对应
Map headTitle = loadHeadTitle(sheet);
//循环行
for (int i = 1; i <= rowCount; i++) {
Row row = sheet.getRow(i);
//空行跳过
if (row == null) {
continue;
}
int cellCount = row.getLastCellNum();
@SuppressWarnings("unchecked")
T instance = (T) entity.newInstance();
int col = 0;
try {
//循环每行单元格
for (; col < cellCount; col++) {
String cellValue = getCellValue(row.getCell(col));
if (null != cellValue) {
this.setEntity(entity, instance, titleToAttr.get(headTitle.get(col)), cellValue);
}
}
result.add(instance);
} catch (Exception e) {
String message="第" + (i + 1) + "行," + headTitle.get(col) + "字段,数据错误!";
log.info(message);
throw new IllegalArgumentException(message);
}
}
excel.close();
return result;
}
/**
* 加载Excel的标题栏
*
* @param sheet
* @return 返回列序号和对于的标题名称Map
*/
private Map loadHeadTitle(Sheet sheet) {
Map map = new HashMap();
Row row = sheet.getRow(0);
int cellCount = row.getLastCellNum();
for (int i = 0; i < cellCount; i++) {
String value = row.getCell(i).getStringCellValue();
if (null == value) {
throw new RuntimeException("Excel导入:标题栏不能为空!");
}
map.put(i, value);
}
return map;
}
/**
* 获取表格列的值
*
* @param cell
* @return
*/
private String getCellValue(Cell cell) {
if (null == cell||"".equals(cell)) {
return "";
}
String value = "";
switch (cell.getCellType()) {
case XSSFCell.CELL_TYPE_BLANK:
//空值
value = null;
break;
case XSSFCell.CELL_TYPE_BOOLEAN:
value = String.valueOf(cell.getBooleanCellValue());
break;
case XSSFCell.CELL_TYPE_NUMERIC:
// 判断当前的cell是否为Date
if (DateUtil.isCellDateFormatted(cell)) {
value = dateFormat.format(cell.getDateCellValue());
} else {
value = String.valueOf((long) cell.getNumericCellValue());
}
break;
case XSSFCell.CELL_TYPE_STRING:
value = cell.getStringCellValue();
break;
case XSSFCell.CELL_TYPE_FORMULA:
log.info("不支持带有函数的单元格!");
throw new IllegalArgumentException("不支持带有函数格式的单元格!");
default:
log.info("单元格格式有误!");
throw new IllegalArgumentException("单元格格式有误!");
}
return value;
}
/**
* 封装实体值
*
* @param clazz
* @param instance
* @param pro
* @param value
* @param
* @throws SecurityException
* @throws NoSuchMethodException
* @throws Exception
*/
private void setEntity(Class> clazz, T instance, String pro, String value) throws SecurityException, NoSuchMethodException, Exception {
String innerPro = null;
String outterPro = null;
if (pro.contains(".")) {
String[] pros = pro.split("\\.");
outterPro = pros[0];
innerPro = pros[1];
// 将成员变量的类型存储到仓库中
storage.storeClass(instance.hashCode() + outterPro, clazz.getDeclaredMethod(this.initGetMethod(outterPro), null).getReturnType());
}
String getMethod = this.initGetMethod(outterPro != null ? outterPro : pro);
Class> type = clazz.getDeclaredMethod(getMethod, null).getReturnType();
Method method = clazz.getMethod(this.initSetMethod(outterPro != null ? outterPro : pro), type);
if (type == String.class) {
method.invoke(instance, value);
} else if (type == int.class || type == Integer.class) {
method.invoke(instance, Integer.parseInt("".equals(value) ? "0" : value));
} else if (type == long.class || type == Long.class) {
method.invoke(instance, Long.parseLong("".equals(value) ? "0" : value));
} else if (type == float.class || type == Float.class) {
method.invoke(instance, Float.parseFloat("".equals(value) ? "0" : value));
} else if (type == double.class || type == Double.class) {
method.invoke(instance, Double.parseDouble("".equals(value) ? "0" : value));
} else if (type == Date.class) {
method.invoke(instance, dateFormat.parse(value));
} else if (type == boolean.class || type == Boolean.class) {
method.invoke(instance, Boolean.parseBoolean("".equals(value) ? "false" : value));
} else if (type == byte.class || type == Byte.class) {
method.invoke(instance, Byte.parseByte(value));
} else {
// 引用类型数据
Object ins = storage.getInstance(instance.hashCode() + outterPro);
this.setEntity(ins.getClass(), ins, innerPro, value);
method.invoke(instance, ins);
}
}
/**
* 初始化set方法
*
* @param field
* @return
*/
private String initSetMethod(String field) {
return "set" + field.substring(0, 1).toUpperCase() + field.substring(1);
}
/**
* 初始化get方法
*
* @param field
* @return
*/
private String initGetMethod(String field) {
return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}
/**
* 处理2003、2007兼容问题
*
* @param inp
* @return
* @throws IOException
* @throws InvalidFormatException
*/
private Workbook create(InputStream inp) throws IOException, InvalidFormatException {
if (!inp.markSupported()) {
inp = new PushbackInputStream(inp, 8);
}
if (POIFSFileSystem.hasPOIFSHeader(inp)) {
return new HSSFWorkbook(inp);
}
if (POIXMLDocument.hasOOXMLHeader(inp)) {
return new XSSFWorkbook(OPCPackage.open(inp));
}
throw new IllegalArgumentException("当前Excel版本poi不能解析!");
}
/**
* 存储bean中的bean成员变量内部类
*/
class BeanStorage {
private Map instances = new HashMap();
public void storeClass(String key, Class> clazz) throws Exception {
if (!instances.containsKey(key)) {
instances.put(key, clazz.newInstance());
}
}
public Object getInstance(String key) {
return instances.get(key);
}
}
}
EntityListToExcelUtil.java:
package com.hanxiaozhang.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.xssf.usermodel.*;
import static java.util.regex.Pattern.compile;
/**
* 功能描述:
* 〈导出Excel〉
*
* @Author:hanxinghua
* @Date: 2020/2/25
*/
public class EntityListToExcelUtil {
private StringBuffer error = new StringBuffer(0);
private EntityListToExcelUtil(){}
private static class Holder{
private static EntityListToExcelUtil INSTANCE=new EntityListToExcelUtil();
}
public static EntityListToExcelUtil getInstance(){
return Holder.INSTANCE;
}
/**
* 将实体类列表entityList转换成excel
* 2003- 版本的excel
*
* @param attrToTitle 包含headMapping信息,key为属性名,value为列名
* @param entityList
* @param excel
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws IOException
*/
@Deprecated
private boolean executeXLS(Map attrToTitle, List entityList, OutputStream excel) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
System.out.println(excel.toString());
// 声明一个工作薄
HSSFWorkbook workbook = new HSSFWorkbook();
// 生成一个表格
HSSFSheet sheet = workbook.createSheet();
// 设置表格默认列宽度为15个字节
sheet.setDefaultColumnWidth(15);
// 产生表格标题行
HSSFRow row = sheet.createRow(0);
int i = 0;
List proList = new ArrayList();
HSSFFont blueFont = workbook.createFont();
blueFont.setColor(HSSFColor.BLUE.index);
for (Map.Entry entry : attrToTitle.entrySet()) {
HSSFCell cell = row.createCell(i);
HSSFRichTextString text = new HSSFRichTextString(entry.getValue());
text.applyFont(blueFont);
cell.setCellValue(text);
proList.add(entry.getKey());
i++;
}
// 遍历集合数据,产生数据行
Iterator it = entityList.iterator();
int index = 0;
while (it.hasNext()) {
index++;
row = sheet.createRow(index);
T t = (T) it.next();
// 利用反射,根据javabean属性的先后顺序,动态调用getXxx()方法得到属性值
for (i = 0; i < proList.size(); i++) {
HSSFCell cell = row.createCell(i);
String propertyName = proList.get(i);
String textValue = null;
try {
textValue = this.getPropertyValue(t, propertyName);
} catch (Exception e) {
e.printStackTrace();
this.error.append("第").append(index + 1).append("行,列名:").append(attrToTitle.get(propertyName)).append(",字段:").append(propertyName).append(",数据错误,跳过!").append("
");
}
// 利用正则表达式判断textValue是否全部由数字组成
if (textValue != null) {
Pattern p = compile("^//d+(//.//d+)?$");
Matcher matcher = p.matcher(textValue);
if (matcher.matches()) {
// 是数字当作double处理
cell.setCellValue(Double.parseDouble(textValue));
} else {
HSSFRichTextString richString = new HSSFRichTextString(
textValue);
cell.setCellValue(richString);
}
}
}
}
workbook.write(excel);
//关闭输出流
excel.close();
return true;
}
/**
* 将实体类列表entityList转换成excel
* 2007+ 版本的excel
*
* @param attrToTitle 包含headMapping信息,key为属性名,value为列名
* @param entityList
* @param excel
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws IOException
*/
public boolean executeXLSX(Map attrToTitle, List entityList, OutputStream excel) throws NoSuchMethodException,
SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, IOException {
// 声明一个工作薄
XSSFWorkbook workbook = new XSSFWorkbook();
// 生成一个表格
XSSFSheet sheet = workbook.createSheet("sheet1");
// 设置表格默认列宽度为15个字节
sheet.setDefaultColumnWidth(30);
// 产生表格标题行
XSSFRow row = sheet.createRow(0);
int i = 0;
List proList = new ArrayList<>();
//设置单元格格式
XSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setWrapText(true);
//设置字体
XSSFFont blueFont = workbook.createFont();
blueFont.setColor(IndexedColors.BLUE.getIndex());
//设置表头
Iterator> itr = attrToTitle.entrySet().iterator();
while (itr.hasNext()){
Map.Entry entry = itr.next();
XSSFCell cell = row.createCell(i);
XSSFRichTextString text = new XSSFRichTextString(entry.getValue());
text.applyFont(blueFont);
cell.setCellValue(text);
proList.add(entry.getKey());
i++;
}
// 遍历集合数据,产生数据行
Iterator it = entityList.iterator();
int index = 0;
while (it.hasNext()) {
index++;
row = sheet.createRow(index);
T t = (T) it.next();
// 利用反射,动态调用getXxx()方法得到属性值
for (i = 0; i < proList.size(); i++) {
XSSFCell cell = row.createCell(i);
cell.setCellStyle(cellStyle);
String propertyName = proList.get(i);
String textValue = null;
try {
textValue = this.getPropertyValue(t, propertyName);
} catch (Exception e) {
e.printStackTrace();
this.error.append("第").append(index + 1).append("行,列名:").append(attrToTitle.get(propertyName)).append(",字段:").append(propertyName).append(",数据错误,跳过!").append("
");
}
// 利用正则表达式判断textValue是否全部由数字组成
if (textValue != null) {
Pattern p = compile("^//d+(//.//d+)?$");
Matcher matcher = p.matcher(textValue);
if (matcher.matches()) {
// 是数字当作double处理
cell.setCellValue(Double.parseDouble(textValue));
} else {
XSSFRichTextString richString = new XSSFRichTextString(textValue);
cell.setCellValue(richString);
}
}
}
}
workbook.write(excel);
//关闭输出流
excel.close();
return true;
}
/**
* 获取实体instance的propertyName属性的值
*
* @param instance
* @param propertyName
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
public String getPropertyValue(T instance, String propertyName)
throws NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
String getMethodName = this.initGetMethod(propertyName);
Class> tCls = instance.getClass();
Method getMethod = null;
Object value = null;
getMethod = tCls.getMethod(getMethodName, new Class[]{});
value = getMethod.invoke(instance, new Object[]{});
String returnType = getMethod.getReturnType().getName();
// 判断值的类型后进行强制类型转换
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String textValue = "";
if(value==null){
return textValue;
}
if ("java.util.Date".equals(returnType)) {
textValue = dateFormat.format(value);
} else {
textValue = value.toString();
}
return textValue;
}
/**
* 返回fiel属性的getXXX方法字符串
*
* @param field
* @return
*/
private String initGetMethod(String field) {
return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}
/**
* @return true 存在错误,false 不存在错误
*/
public boolean hasError() {
return error.capacity() > 0;
}
/**
* 获得错误信息
*
* @return
*/
public StringBuffer getError() {
return error;
}
}
在实际的环境中,导入的Excel可能数据量很大,如果已经对代码进行批量插入、重复数据只查一次、代码逻辑等优化,还没有降低程序处理的时间,那我们就可以考虑使用多线程来处理数据。但是,多线程是一把双刃剑,使用得当则提高处理能力,降低程序运行时间;反之,会造成线程不安全,脏数据等致命问题。在单线程处理数据时,我们通常使用事务来避免脏数据,但是在多线程下事务可能就不好用了,原因是数据交给了多个线程去处理,事务只能控制一个线程内的数据,一个线程出错了,另一个线程的数据可能已经出入库了,破坏了数据的完整性。如何在多线程下使事务回滚呢?这里需要用到同步锁、等待与通知、Volatile关键字等相关知识。原理是这样:我们创建一个多线程结束标识的类(MultiThreadEndFlag.java),这个类会有线程总个数、失败线程数据、是否全部成功等属性(它们都使用Volatile关键字,保证线程安全),每个线程执行完业务操作后都会调用此类等待结束的方法(synchronized void waitForEnd(int resultFlag)),说明自己已经完成业务,waitForEnd方法会调用wait(),使线程停止后面代码的运行,放弃CUP运行时间进入等待池。如果每个线程都运行完业务逻辑,代码会调用多线程标识类的结束方法(end()),end方法会调用notifyAll(),通知所有线程继续运行,这时,每个线程会通过多线程标识类中是否全部成功属性去判断是否成功,如果不是成功,线程自己会自己抛出异常,使事务回滚。上面讲了如何使多线程事务回滚,下面说一说如何使用多线程,我这里会用到Executor相关线程池的知识,如何不太熟悉可以自己去学习一下。此外,我这套方法还支持业务判断有问题数据导出并提示错误原因的功能,该功能主要是通过Callable创建线程,可以通过Future获取返回值的知识实现的,具体代码如下:
MultiThreadEndFlag.java:
package com.hanxiaozhang.importexcel;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
/**
* 功能描述:
* 〈多线程结束标志〉
*
* @Author:hanxinghua
* @Date: 2020/2/23
*/
@Slf4j
public class MultiThreadEndFlag {
/**
* 是否解除等待
*/
private volatile boolean releaseWaitFlag = false;
/**
* 是否全部执行成功
*/
private volatile boolean allSuccessFlag = false;
/**
* 线程个数
*/
private volatile int threadCount = 0;
/**
* 失败个数
*/
private volatile int failCount = 0;
/**
* 初始化子线程的总数
* @param count
*/
public MultiThreadEndFlag(int count){
threadCount = count;
}
public boolean allSuccessFlag() {
return allSuccessFlag;
}
/**
* 等待全部结束
* @param resultFlag
*/
public synchronized void waitForEnd(int resultFlag){
//统计失败的线程个数
if(resultFlag==0){
failCount++;
}
threadCount--;
while (!releaseWaitFlag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 执行结束通知
*/
public synchronized void go(){
releaseWaitFlag = true;
//结果都显示成功
allSuccessFlag = (failCount == 0);
notifyAll();
}
/**
* 等待结束
*/
public void end(){
while (threadCount > 0){
waitFunc(50);
}
log.info("线程全部执行完毕通知");
go();
}
/**
* 等待
*/
private void waitFunc(long millis){
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ImportExcelTask.java:
package com.hanxiaozhang.importexcel;
import java.util.List;
import java.util.concurrent.Callable;
/**
* 〈功能描述〉
* 〈导入Excel任务〉
*
* @author hanxinghua
* @create 2020/2/23
* @since 1.0.0
*/
public class ImportExcelTask implements Callable {
/**
* 保存Excel服务
*/
private SaveExcelService excelService;
/**
* 数据集合
*/
private List list;
/**
* 多线程数据结束标志
*/
private MultiThreadEndFlag flag;
/**
* 构造函数
*
* @param excelService
* @param list
* @param flag
*/
public ImportExcelTask(SaveExcelService excelService,List list,MultiThreadEndFlag flag){
this.excelService=excelService;
this.list=list;
this.flag=flag;
}
@Override
public ErrorInfoEntity call() throws Exception {
return excelService.batchSave(list,flag);
}
}
ErrorInfoEntity.java:
package com.hanxiaozhang.importexcel;
import lombok.Data;
import java.util.List;
/**
* 〈一句话功能简述〉
* 〈错误信息实体〉
*
* @author hanxinghua
* @create 2020/2/23
* @since 1.0.0
*/
@Data
public class ErrorInfoEntity {
/**
* 业务上判断有错误的数据集合
*/
private List errorList;
}
SaveExcelService.java:
package com.hanxiaozhang.importexcel;
import java.util.List;
/**
* 〈一句话功能简述〉
* 〈保存ExcelService〉
*
* @author hanxinghua
* @create 2020/2/23
* @since 1.0.0
*/
public interface SaveExcelService {
ErrorInfoEntity batchSave(List list, MultiThreadEndFlag flag) throws Exception;
}
DictSaveExcelServiceImpl.java:
package com.hanxiaozhang.importexcel;
import com.hanxiaozhang.dictcode.dao.DictOneDao;
import com.hanxiaozhang.dictcode.domain.DictDO;
import com.hanxiaozhang.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* 〈一句话功能简述〉
* 〈数据字典数据导入〉
*
* @author hanxinghua
* @create 2020/2/23
* @since 1.0.0
*/
@Slf4j
@Service
public class DictSaveExcelServiceImpl implements SaveExcelService {
@Resource
private DictOneDao dictOneDao;
@Override
@Transactional(rollbackFor = Exception.class)
public ErrorInfoEntity batchSave(List list, MultiThreadEndFlag flag) throws Exception {
int resultFlag = 0;
try {
//创建返回错误信息实体
ErrorInfoEntity errorInfoEntity = new ErrorInfoEntity();
//业务操作
List errorList = handleDict(list);
//赋值错误数据
errorInfoEntity.setErrorList(errorList);
//操作成功
resultFlag = 1;
//等待其他线程完成操作
flag.waitForEnd(resultFlag);
//其他线程异常手工回滚
if (resultFlag == 1 && !flag.allSuccessFlag()) {
String message = "子线程未全部执行成功,对线程[" + Thread.currentThread().getName() + "]进行回滚";
log.info(message);
throw new Exception(message);
}
return errorInfoEntity;
} catch (Exception e) {
log.error(e.toString());
//本身线程异常抛出异常,并且没有调用flag.waitForEnd()时触发
if (resultFlag == 0) {
flag.waitForEnd(resultFlag);
}
throw e;
}
}
/**
* 处理相关数据
*
* @param list
* @return
*/
private List handleDict(List list) {
List errorList=new ArrayList<>();
list.forEach(x->{
boolean flag=true;
List errorMsg=new ArrayList<>();
//模拟一个业务数据错误,姓名不能为空
if (StringUtil.isBlank(x.getName())) {
errorMsg.add("姓名不能为空!");
flag=false;
}
//模拟一个业务数据错误,类型不能为空
if (StringUtil.isBlank(x.getType())){
errorMsg.add("类型不能为空!");
flag=false;
}
if (flag){
dictOneDao.save(x);
}else {
x.setRemarks(String.join("\n",errorMsg));
errorList.add(x);
}
});
return errorList;
}
}
ImportExcelExecutor.java:
package com.hanxiaozhang.importexcel;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import static java.util.concurrent.Executors.*;
/**
* 〈一句话功能简述〉
* 〈导入Excel执行器〉
*
* @author hanxinghua
* @create 2020/2/23
* @since 1.0.0
*/
@Slf4j
public class ImportExcelExecutor {
private static int maxThreadCount=10;
/**
* 执行方法(分批创建子线程)
* @param saveService 保存的服务
* @param lists 数据List
* @param groupLen 分组的长度
* @return
* @throws ExecutionException
* @throws InterruptedException
*/
public static List execute(SaveExcelService saveService, List lists, int groupLen) throws ExecutionException, InterruptedException {
if(lists==null || lists.size()==0){
return null;
}
List errorList=new ArrayList<>();
//创建一个线程池,最大10个线程
ExecutorService executorService = newFixedThreadPool(maxThreadCount);
//创建一个Future集合
List> futures = new ArrayList<>();
//集合的元素个数
int size = lists.size();
//适配线程池数与分组长度
//Math.ceil()对小数向下“”取整”
int batches = (int) Math.ceil(size * 1.0 /groupLen);
//分组超长最大线程限制,则设置分组数为10,计算分组集合尺寸
if(batches>maxThreadCount){
batches = maxThreadCount;
groupLen = (int) Math.ceil(size * 1.0 /batches);
}
log.info("总条数:[{}],批次数量:[{}],每批数据量:[{}]",size,batches,groupLen);
MultiThreadEndFlag flag = new MultiThreadEndFlag(batches);
int startIndex, toIndex, maxIndex = lists.size();
for(int i=0;i maxIndex) {
toIndex = maxIndex;
}
//截取数组
List temp = lists.subList(startIndex,toIndex);
if(temp == null || temp.size()==0){
continue;
}
futures.add(executorService.submit(new ImportExcelTask(saveService,temp,flag)));
}
flag.end();
//子线程全部等待返回(存在异常,则直接抛向主线程)
for(Future future:futures){
errorList.addAll(future.get().getErrorList());
}
//所有线程返回后,关闭线程池
executorService.shutdown();
return errorList;
}
}
使用这块也没有什么好说的,大家的使用方法都类型。这块要说一点Ajax不支持下载的功能,如果不知道原因,可以自己搜索一下。我这块会用到下载,大家可以去代码中看一下怎么处理的,相关代码如下:
Controller:
@GetMapping
public String excelTest(){
return "importExcel";
}
@ResponseBody
@PostMapping("/importExcel")
public R importExcel(@RequestParam(value = "file") MultipartFile file) {
if (file == null) {
return R.error(1, "文件不能为空");
}
if (StringUtil.isBlank(file.getOriginalFilename()) || file.getSize() == 0) {
return R.error(1, "文件不能为空");
}
long startTime = System.currentTimeMillis();
log.info("Excel开始导入,logId:[{}]", startTime);
//数据导入处理
R r = dictService.importExcel(file);
if ("1".equals(r.get("code").toString())) {
Map map = (Map) r.get("map");
map.put("logId",startTime);
log.info("Excel导入出错,logId:[{}]", startTime);
return R.error(1, map, "导入时有错误信息");
}
long endTime = System.currentTimeMillis();
log.info("Excel导入成功,logId:[{}],导入Excel耗时(ms):[{}]", startTime,endTime-startTime);
return r;
}
@ResponseBody
@PostMapping("/exportExcel")
public void exportExcel(@RequestParam("data") String data, HttpServletResponse response) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//将Json字符串转Map
Map params = JsonUtil.jsonToMapSO(data);
log.info("Excel导出错误信息,logId:[{}]", params.get("logId").toString());
//response设置返回类型
setDownloadExcelResponse(response, params.get("fileName").toString());
//数据导出为excel
EntityListToExcelUtil.getInstance().
executeXLSX(JsonUtil.jsonToLinkedHashMapSS(params.get("title").toString()),
JsonUtil.jsonToList(params.get("errorData").toString(), DictDO.class),
response.getOutputStream());
}
/**
* 设置下载文件响应信息
*
* @param response
* @param fileName
*/
private void setDownloadExcelResponse(HttpServletResponse response, String fileName) {
try {
fileName = new String(fileName.getBytes(), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
log.error("该文件[{}]不支持此编码转换,异常消息:[{}]",fileName,e.getMessage());
}
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
//使用Content-Disposition,一定要确保没有禁止浏览器缓存的操作
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "No-cache");
response.setDateHeader("Expires", 0);
}
Service:
@Override
@Transactional(rollbackFor = Exception.class)
public R importExcel(MultipartFile file) {
try {
//读取Excel中数据
ArrayList list = ExcelToEntityListUtil.getInstance().execute(DictDO.class, file.getInputStream(), initTitleToAttr());
//多线程处理数据,并导出错误数据
List errorList = ImportExcelExecutor.execute(dictSaveExcelServiceImpl, list, 10);
//封装错误数据
if (errorList!=null&&!errorList.isEmpty()) {
Map map = new HashMap();
map.put("errorData", errorList);
map.put("title", initAttrToTitle());
map.put("fileName", "有问题数据.xlsx");
return R.error(map);
}
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return R.ok();
}
private Map initTitleToAttr(){
Map map = new LinkedHashMap<>(8);
map.put("姓名","name");
map.put("值","value");
map.put("类型","type");
map.put("描述","description");
map.put("时间","createDate");
return map;
}
private Map initAttrToTitle(){
Map map = new LinkedHashMap<>(8);
map.put("name","姓名");
map.put("value","值");
map.put("type","类型");
map.put("description","描述");
map.put("createDate","时间");
map.put("remarks","数据问题备注");
return map;
}
页面:
Title
Js:
function importExcel() {
$.ajax({
type: "POST",
dataType: "json",
cache: false,
processData: false,
contentType: false,
url: "/importExcel",
data: new FormData($('#importPlan')[0]),
success: function (r) {
if (r.code == 0) {
alert("导入成功");
} else {
$("#data").val(JSON.stringify(r.map));
$("#exportForm").submit();
}
}
});
}
https://gitee.com/hanxinghua2017/springboot_demo.git springboot-multi-thread模块