看如下Controller层接收到Excel表路径的操作如下:
/**
* 数据导入
* @return
*/
@PostMapping("/import")
public ResultJson importExcel(@RequestParam("file") MultipartFile file) throws IOException {
//下面路径写死了,可以通过上传到服务器获取到指定路径
String filepath = "C:\\Users\\xxx\\Desktop\\" + file.getOriginalFilename();
//通过ExcelUtil的构造器给泛型赋值,也将反射对象赋值给ExcelUtil类
ExcelUtil<Test> testExcelUtil = new ExcelUtil<>(Test.class);
//调用对象里的importExcel4方法获取到Excel表里的数据(进行了封装)
List<Test> list = testExcelUtil.importExcel4(filepath);
//使用mybatis-plus的批量插入到数据库中
boolean b = testService.saveBatch(list);
return ResultJson.ok();
}
ExcelUtil类的构造器如下:
public class ExcelUtil<T> {
/**
* 实体对象
*/
public Class<T> clazz;
public ExcelUtil(Class<T> clazz) {
this.clazz = clazz;
}
}
通过Excel表地址获取到Excel表中的信息,方法如下:
/**
* @Description:获取IO流中的数据,组装成List<List<Object>>对象
* @param in,fileName
* @return
* @throws IOException
*/
public static List<List<Object>> getListByExcel(InputStream in, String fileName) throws Exception{
List<List<Object>> list = null;
//创建Excel工作薄
Workbook work = getWorkbook(in,fileName);
if(null == work){
throw new Exception("创建Excel工作薄为空!");
}
Sheet sheet = null; //页数
Row row = null; //行数
Cell cell = null; //列数
list = new ArrayList<List<Object>>();
//遍历Excel中所有的sheet
for (int i = 0; i < work.getNumberOfSheets(); i++) {
sheet = work.getSheetAt(i);
if(sheet==null){continue;}
//遍历当前sheet中的所有行
for (int j = sheet.getFirstRowNum(); j <= sheet.getLastRowNum(); j++) {
row = sheet.getRow(j);
if(row==null){continue;}
//遍历所有的列
List<Object> li = new ArrayList<Object>();
for (int y = row.getFirstCellNum(); y < row.getLastCellNum(); y++) {
cell = row.getCell(y);
li.add(getValue(cell));
}
list.add(li);
}
}
return list;
}
/**
* @Description:根据文件后缀,自适应上传文件的版本
* @param inStr,fileName
* @return
* @throws Exception
*/
public static Workbook getWorkbook(InputStream inStr,String fileName) throws Exception{
Workbook wb = null;
String fileType = fileName.substring(fileName.lastIndexOf("."));
if(excel2003L.equals(fileType)){
wb = new HSSFWorkbook(inStr); //2003-
}else if(excel2007U.equals(fileType)){
wb = new XSSFWorkbook(inStr); //2007+
}else{
throw new Exception("解析的文件格式有误!");
}
return wb;
}
/**
* @Description:对表格中数值进行格式化
* @param cell
* @return
*/
//解决excel类型问题,获得数值
public static String getValue(Cell cell) {
String value = "";
if(null==cell){
return value;
}
switch (cell.getCellType()) {
//数值型
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
//如果是date类型则 ,获取该cell的date值
Date date = DateUtil.getJavaDate(cell.getNumericCellValue());
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
value = format.format(date);;
}else {// 纯数字
BigDecimal big=new BigDecimal(cell.getNumericCellValue());
value = big.toString();
//解决1234.0 去掉后面的.0
if(null!=value&&!"".equals(value.trim())){
String[] item = value.split("[.]");
if(1<item.length&&"0".equals(item[1])){
value=item[0];
}
}
}
break;
//字符串类型
case STRING:
//如果是string类型进行下划线转驼峰处理
value = underlineToHump(cell.getStringCellValue());
break;
// 公式类型
case FORMULA:
//读公式计算值
value = String.valueOf(cell.getNumericCellValue());
if (value.equals("NaN")) {// 如果获取的数据值为非法值,则转换为获取字符串
value = cell.getStringCellValue();
}
break;
// 布尔类型
case BOOLEAN:
value = " "+ cell.getBooleanCellValue();
break;
default:
value = cell.getStringCellValue();
}
if("null".endsWith(value.trim())){
value="";
}
return value;
}
上面调用了下划线转驼峰处理如下:
?
//下划线转驼峰正则表达式
private static Pattern UNDERLINE_PATTERN = Pattern.compile("_([a-z])");
/**
* 根据传入的带下划线的字符串转化为驼峰格式
* @param str
* @author mrf
* @return
*/
public static String underlineToHump (String str) {
//正则匹配下划线及后一个字符,删除下划线并将匹配的字符转成大写
Matcher matcher = UNDERLINE_PATTERN.matcher(str);
StringBuffer sb = new StringBuffer(str);
if (matcher.find()) {
sb = new StringBuffer();
//将当前匹配的子串替换成指定字符串,并且将替换后的子串及之前到上次匹配的子串之后的字符串添加到StringBuffer对象中
//正则之前的字符和被替换的字符
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
//把之后的字符串也添加到StringBuffer对象中
matcher.appendTail(sb);
} else {
//去除除字母之外的前面带的下划线
return sb.toString().replaceAll("_", "");
}
return underlineToHump(sb.toString());
}
这里的代码是通过传入输入流和文件位置获取到Excel表中的数据,并对数据进行一些处理,最后获取到的Excel表中的数据都会转换为String类型,想进行插入到数据库我们还要对获取到的数据进行类型转换。
在转换前我们需要先将表头信息和我们的实体类进行绑定再对实体类进行赋值,如下图Excel表
?
如上表头信息,也就是第一行数据我们可以对下划线进行转驼峰处理,通过反射在实体类中找到属性和表头信息一致的进行绑定,在对其他数据进行赋值到实体类中。
但是一般用户都会使用中文作为表头信息如下图:
?
我们可以 给实体类添加注解,在注解的name属性中写上对应的中文,可以通过反射获取到注解信息,和里面的值进行匹配。
实体类如下所示:
@Data
@TableName("t_test")
@Excel(name = "测试")
public class Test {
@TableId(type = IdType.AUTO)
@Excel(name = "序号", cellType = Excel.ColumnType.NUMERIC)
private Long id;
@Excel(name = "姓名")
private String name;
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
@Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@TableField(exist=false)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeBegin;
@TableField(exist=false)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTimeEnd;
}
给各个属性添加了Excel注解
然后我最上面Controller调用的方法是将获取到的?Excel表的信息赋值给实体类,再把这些类放到List集合中,再对它们进行数据库的批量插入操作。
而我们如何知道Excel表中每列信息都对应哪个类中的哪个属性呢?
我们再Controller调用ExcelUtil类时已经对泛型进行了赋值,告诉它要处理哪个实类,我们在ExcelUtil表中获取到实体类后我们就可以对它进行反射获取到类中的属性名和注解信息,而我们Excel表中第一行和它相等的我们就认为这一列数据属于这个属性,对它进行赋值,代码如下:
/**
* 对获取到的Excel表的数据进行封装,返回一个可批量插入的集合
*/
public List<T> importExcel4(String filepath){
//创建一个可以返回的集合,最后返回它
List<T> data = new ArrayList<>();
FileInputStream inputStream = null;
try {
//创建一个输入流,调用获取Excel表内信息的方法获取到Excel表中的数据
inputStream = new FileInputStream(new File(filepath));
List<List<Object>> list = ExcelUtil.getListByExcel(inputStream, filepath);
//获取到的Excel表中的信息第一行是和实体类属性或注解进行校验是否相等的,单独拿出来
List<Object> firstRows = null;
if(list != null && list.size() > 0){
firstRows = list.get(0);
}
//对其他每行数据进行赋值给实体类并添加到data集合中
for (int i = 1; i < list.size(); i++) {
List<Object> rows = list.get(i);
//通过反射实例化要进行处理的类
T object = clazz.newInstance();
//对每行的每列数据进行处理
for (int j = 0; j < rows.size(); j++) {
String cellVal = (String) rows.get(j);
//调用此方法进行给实体类属性赋值
setFieldValueByFieldNameOrAnnotation(object, firstRows.get(j).toString().trim(), cellVal);
}
data.add(object);
object = null;
System.out.println(data);
}
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
/**
* 通过反射判断注解或属性名给实体类赋值,
* 对获取到的数据String类型进行类型转换,类型要和实体类属性类型一致
* @param fieldName
* @param val
*/
private Object setFieldValueByFieldNameOrAnnotation(Object object, String fieldName, Object val) {
try {
//通过反射获取到类中全部属性
Field[] fields = clazz.getDeclaredFields();
//遍历对每个属性进行判断
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
//通过反射获取到属性中注解,如果没有此注解直接进入下一个属性的判断
Excel excel= field.getAnnotation(Excel.class);
if(excel == null) continue;
//判断Excel表的第一行的某一列是否和获取到属性或注解信息一致,一致进行封装处理
//将String类型装换为其相对应的类型
if(fieldName.equals(field.getName()) || fieldName.equals(excel.name())){
Object typeVal = null;
String type = field.getType().getName();
if(type.equals("long") || type.equals("java.lang.Long")){
typeVal = Long.parseLong((String) val);
}else if(type.equals("java.time.LocalDateTime")){
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
typeVal = LocalDateTime.parse((CharSequence) val,df);
}else if(type.equals("int") ||type.equals("java.lang.Integer")){
typeVal = Integer.parseInt((String) val);
}else if(type.equals("boolean") || type.equals("java.lang.Boolean")) {
typeVal = (Boolean) val;
}else if(type.equals("java.lang.String")){
typeVal = val;
}
//反射对属性进行赋值
field.setAccessible(true);
field.set(object, typeVal);
return object;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clazz;
}
|