该文主要是教大家如何在springboot+mybatis环境下完成对Excel表格的批量导入,之前关于批量导入的文章我也看过不少到多数都是有点问题的不够全面,比如Excel中存在图片要怎么处理?Excel两种版本不兼容怎么办?所以借鉴了多种方法最后总结了一个比较完善的方法,而且是附带图片的,并区别两种Excel格式的导入方式都没问题哦。
下面开始搭建环境完成准备工作
具体得springboot+mybatis整合环境我这里就不在介绍了,有需要的可以看之前发布的整合环境搭建篇
采用的是第三方库所以需要导入一下依赖,这里需要注意一下包的版本问题
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
这里需要用到插入操作所以写好插入sql语句
mapper.xml
<insert id="insertData" parameterType="com.example.demo.model.User" >
insert into user
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="name != null" >
name,
</if>
<if test="sex != null" >
sex,
</if>
<if test="age != null" >
age,
</if>
<if test="city != null" >
city,
</if>
<if test="address != null" >
address,
</if>
<if test="createTime != null" >
create_time,
</if>
<if test="updateTime != null" >
update_time,
</if>
<if test="picUrl != null" >
pic_url,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=INTEGER},
</if>
<if test="name != null" >
#{name,jdbcType=VARCHAR},
</if>
<if test="sex != null" >
#{sex,jdbcType=VARCHAR},
</if>
<if test="age != null" >
#{age,jdbcType=INTEGER},
</if>
<if test="city != null" >
#{city,jdbcType=VARCHAR},
</if>
<if test="address != null" >
#{address,jdbcType=VARCHAR},
</if>
<if test="createTime != null" >
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null" >
#{updateTime,jdbcType=TIMESTAMP},
</if>
<if test="picUrl != null" >
#{picUrl,jdbcType=VARCHAR},
</if>
</trim>
</insert>
mapper
//根据ID插入数据
void insertData(User user);
这里用到了lombok插件,没有的请自己手动写上set和get方法
@Data
public class User {
private Integer id;
private String name;
private String sex;
private Integer age;
private String city;
private String address;
private Date createTime;
private Date updateTime;
private String picUrl;
}
下面就开始撸代码吧
这是封装的一个返回值工具类,符合RESTful形式,ps:我也忘了哪里来的,反正一直在用
package com.example.demo.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.Serializable;
import java.util.List;
public class E3Result implements Serializable{
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
//响应数据长度
private Integer total;
// 响应中的数据
private Object data;
public static E3Result build(Integer status, String msg, Object data) {
return new E3Result(status, msg, data);
}
public static E3Result ok(Integer total,Object data) {
return new E3Result(200,"OK",total,data);
}
public static E3Result ok(Object data) {
return new E3Result(data);
}
public static E3Result ok() {
return new E3Result(null);
}
public E3Result() {
}
public static E3Result build(Integer status, String msg) {
return new E3Result(status, msg, null);
}
public E3Result(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public E3Result(Integer status, String msg, Integer total, Object data) {
this.status = status;
this.msg = msg;
this.total=total;
this.data = data;
}
public E3Result(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
}
// public Boolean isOK() {
// return this.status == 200;
// }
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static E3Result formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, E3Result.class);
}
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (clazz != null) {
if (data.isObject()) {
obj = MAPPER.readValue(data.traverse(), clazz);
} else if (data.isTextual()) {
obj = MAPPER.readValue(data.asText(), clazz);
}
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
public static E3Result format(String json) {
try {
return MAPPER.readValue(json, E3Result.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static E3Result formatToList(String jsonData, Class<?> clazz) {
try {
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (data.isArray() && data.size() > 0) {
obj = MAPPER.readValue(data.traverse(),
MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
}
具体接口我这里就不写啦,大家自己完善一下吧
@Service
public class DemoServiceImpl implements DemoService {
private static Map<String, String> pictureMap=new HashMap<>();//这个用来存放图片路径的
private Workbook workbook;
@Autowired
private UserMapper userMapper;
public E3Result getExcel(MultipartFile file) throws Exception {
// 1、判断表格是xlsx格式还是xls格式
if (StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), "xlsx")) {
workbook = new XSSFWorkbook(file.getInputStream());
System.out.println("表格是xlsx格式");
} else if (StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), "xls")) {
workbook = new HSSFWorkbook(file.getInputStream());
System.out.println("表格是xls格式");
} else {
return E3Result.build(400,"文件类型不符合");
}
// 2、根据文件类型读取Excel全图片 文件类型不同读取方式不同
if (StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), "xlsx")) {
getPicturesXLSX(workbook);
} else {
getPicturesXLS(workbook);
}
// 3、获取第一张工作表
Sheet sheet = workbook.getSheetAt(0);
// 4、获取行数
int rowNum = sheet.getLastRowNum();
// 5、遍历每一行 第一行表头跳过
for (int i = 1; i <= rowNum; i++) {
Row row = sheet.getRow(i);
//row.getCell(2) 这个是获取第几列 例如这里就是第三列 下标从0开始
//getCellValue是自动判断表格数据并转成String返回
//6.获取到数据填充到实体类插入
User user = new User();
user.setId(null);
user.setName(getCellValue(row.getCell(2)));
user.setAge(Integer.valueOf(getCellValue(row.getCell(3))));
user.setSex(getCellValue(row.getCell(4)));
user.setAddress(getCellValue(row.getCell(5)));
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
//这里获取图片的路径
user.setPicUrl(pictureMap.get(i+"+"+1));
//7.插入
userMapper.insertData(user);
}
return E3Result.ok();
}
/**
* cell数据格式转换
*
* @param cell
* @return
*/
public String getCellValue(Cell cell) {
switch (cell.getCellType()) {
case Cell.CELL_TYPE_NUMERIC: // 数字
//如果为时间格式的内容
if (HSSFDateUtil.isCellDateFormatted(cell)) {
//注:format格式 yyyy-MM-dd hh:mm:ss 中小时为12小时制,若要24小时制,则把小h变为H即可,yyyy-MM-dd HH:mm:ss
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return sdf.format(HSSFDateUtil.getJavaDate(cell.
getNumericCellValue())).toString();
} else {
return new DecimalFormat("0").format(cell.getNumericCellValue());
}
case Cell.CELL_TYPE_STRING: // 字符串
return cell.getStringCellValue();
case Cell.CELL_TYPE_BOOLEAN: // Boolean
return cell.getBooleanCellValue() + "";
case Cell.CELL_TYPE_FORMULA: // 公式
return cell.getCellFormula() + "";
case Cell.CELL_TYPE_BLANK: // 空值
return "";
case Cell.CELL_TYPE_ERROR: // 故障
return null;
default:
return null;
}
}
/**
* 获取Excel2003的图片
*
* @param workbook
*/
public void getPicturesXLS(Workbook workbook) throws IOException {
List<HSSFPictureData> pictures = (List<HSSFPictureData>) workbook.getAllPictures();
HSSFSheet sheet = (HSSFSheet) workbook.getSheetAt(0);
if (pictures.size() != 0) {
for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) {
HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor();
//获取图片对应所在行
Row row=sheet.getRow(anchor.getRow1());
if (shape instanceof HSSFPicture) {
HSSFPicture pic = (HSSFPicture) shape;
int pictureIndex = pic.getPictureIndex() - 1;
HSSFPictureData picData = pictures.get(pictureIndex);
//以图片所在行列为键值,以图片路径为value值存取
//printImg()是将图片数据及图片名称传入,保存图片 这里我的图片名称为"_+人名" 返回图片路径
pictureMap.put(anchor.getRow1()+"+"+anchor.getCol1(), printImg(picData,"_"+getCellValue(row.getCell(2))));
}
}
}
}
/**
* 获取Excel2007的图片
*
* @param workbook
*/
public void getPicturesXLSX(Workbook workbook) throws IOException {
XSSFSheet xssfSheet = (XSSFSheet) workbook.getSheetAt(0);
for (POIXMLDocumentPart dr : xssfSheet.getRelations()) {
if (dr instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) dr;
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
XSSFPicture pic = (XSSFPicture) shape;
XSSFClientAnchor anchor = pic.getPreferredSize();
CTMarker ctMarker = anchor.getFrom();
//获取图片对应所在行
Row row=xssfSheet.getRow(ctMarker.getRow());
//以图片所在行列为键值,以图片路径为value值存取
//printImg()是将图片数据及图片名称传入,保存图片 这里我的图片名称为"_+人名" 返回图片路径
pictureMap.put(ctMarker.getRow()+"+"+ctMarker.getCol(), printImg(pic.getPictureData(),"_"+getCellValue(row.getCell(2))));
}
}
}
}
/**
* 保存图片并返回存储地址
*
* @param pic
* @return
*/
public String printImg(PictureData pic,String name) throws IOException {
String namePath=name.replace("/","_");
String ext = pic.suggestFileExtension(); //图片格式
// 生成目录
File path=new File(ResourceUtils.getURL("classpath:").getPath());
if(!path.exists()){
path=new File("");
}
//如果上传目录为/static/photo/,则可以如下获取
File upload=new File(path.getAbsolutePath(),"static/photo");
if(!upload.exists()) {
upload.mkdirs();
}
String filePath=upload.getAbsolutePath()+"\\"+ namePath + "." + ext;
byte[] data = pic.getData();
FileOutputStream out = new FileOutputStream(filePath);
out.write(data);
out.close();
return "/photo/"+namePath + "." + ext;
}
}
@Controller
public class ExcelController {
@Autowired
private DemoService demoService;
@RequestMapping(value = "/")
public String index(){
return "demo.html";
}
//批量导入
@ResponseBody
@RequestMapping(value = "/upload/excel",method = RequestMethod.POST)
public E3Result upload(MultipartFile file, Model model) throws Exception {
return demoService.getExcel(file);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form enctype="multipart/form-data" method="post" action="/upload/excel">
文件
<input type="file" name="file" /> <input type="submit" value="上传" />
</form>
</body>
</html>
这里需要注意!!!
1.图片不能越界
2.单元格不能合并
这样一个简单的批量导入就实现啦,当然这个只是演示,实际中还有很多问题是需要处理的,例如Excel中的序列号是不能做为主键的,但又存在数据可能重复插入等问题,所以这些大家需要根据实际情况去做处理,这里就不一一详细介绍了。源码还没上传到git,需要的可以提出来,Thanks♪(・ω・)ノ