SpirngBoot 集成Jeecg-autopoi 和 EasyExcel 3.1.1 实现导入导出
easyexcel
快速、简洁、解决大文件内存溢出的java处理Excel工具
项目地址:https://gitcode.com/gh_mirrors/ea/easyexcel
免费下载资源
·
Jeecg-autopoi 1.3.4 和 EasyExcel 3.1.1 实现导入导出
Jeecg-autopoi
1. 导入依赖
版本可根据自己选择
<dependency>
<groupId>org.jeecgframework</groupId>
<artifactId>autopoi-web</artifactId>
<version>1.3.4</version>
</dependency>
2. 在实体类上添加注解
@Data
public class SysUser implements Serializable {
/**id*/
private String id;
/**登录账号 */
@Excel(name = "登录账号", width = 15)
private String username;
/**真实姓名*/
@Excel(name = "真实姓名", width = 15)
private String realname;
/**头像*/
@Excel(name = "头像", width = 15)
private String avatar;
/**生日*/
@Excel(name = "生日", width = 15, format = "yyyy-MM-dd")
private Date birthday;
/**性别(1:男 2:女)*/
@Excel(name = "性别", width = 15,dicCode="sex")
private Integer sex;
/**电子邮件*/
@Excel(name = "电子邮件", width = 15)
private String email;
/**电话*/
@Excel(name = "电话", width = 15)
private String phone;
/**状态(1:正常 2:冻结 )*/
@Excel(name = "状态", width = 15,replace={"正常_1","冻结_0"})
private Integer status;
3. 导入Excel
@ApiOperation(value = "导入")
@PostMapping("importExcel")
public void importExcel(MultipartFile file) {
//设置参数
ImportParams params = new ImportParams();
params.setTitleRows(2); // 表格标题行数,默认0
params.setHeadRows(2); // 设置表头行数,默认1
params.setNeedSave(true);
try {
long start = System.currentTimeMillis();
List<SysUser> list = ExcelImportUtil.importExcel(file.getInputStream(), SysUser.class, params);
//获取到list然后存入数据库
list.forEach(a -> {
sysUserMapper.insert(a);
});
log.info("消耗时间" + (System.currentTimeMillis() - start) + "毫秒");
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
} finally {
try {
file.getInputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 导出Excel
@ApiOperation(value = "导出")
@GetMapping("exportExcel")
public ModelAndView exportExcel(HttpServletRequest request, HttpServletResponse response) {
ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
List<Activity> pageList = activityMapper.selectList(null);
//导出文件名称
mv.addObject(NormalExcelConstants.FILE_NAME, "导出Excel文件名字");
//注解对象Class
mv.addObject(NormalExcelConstants.CLASS, Activity.class);
//自定义表格参数
mv.addObject(NormalExcelConstants.PARAMS, new ExportParams("自定义导出Excel模板内容标题",
"导出人:ddz", "自定义Sheet名字"));
//导出数据列表
mv.addObject(NormalExcelConstants.DATA_LIST, pageList);
return mv;
}
参考文档:JEECG开源社区
EasyExcel
1. 导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
2. 宽度自适应策略
我这里采用的是自适应宽度,如果想单独设置每个字段的宽度,可以在实体类字段上加注解。
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 宽度自适应策略
*
* @author Windows
*/
public class ExcelWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
// 单元格的最大宽度
private static final int MAX_COLUMN_WIDTH = 200;
// 缓存(第一个Map的键是sheet的index, 第二个Map的键是列的index, 值是数据长度)
private final Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>(8);
// 重写设置列宽的方法
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder,
List<WriteCellData<?>> cellDataList,
Cell cell,
Head head,
Integer relativeRowIndex,
Boolean isHead) {
boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
// 当时表头或者单元格数据列表有数据时才进行处理
if (needSetWidth) {
Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>(16));
// 获取数据长度
Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
if (columnWidth >= 0) {
if (columnWidth > MAX_COLUMN_WIDTH) {
columnWidth = MAX_COLUMN_WIDTH;
}
// 确保一个列的列宽以表头为主,如果表头已经设置了列宽,单元格将会跟随表头的列宽
Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
// 如果使用EasyExcel默认表头,那么使用columnWidth * 512
// 如果不使用EasyExcel默认表头,那么使用columnWidth * 256
// 如果是自己定义的字体大小,可以再去测试这个参数常量
writeSheetHolder
.getSheet()
.setColumnWidth(cell.getColumnIndex(), columnWidth * 512);
}
}
}
}
/**
* 获取当前单元格的数据长度
*/
private Integer dataLength(List<WriteCellData<?>> cellDataList,
Cell cell,
Boolean isHead) {
if (isHead) {
return cell.getStringCellValue().getBytes().length;
} else {
WriteCellData cellData = cellDataList.get(0);
CellDataTypeEnum type = cellData.getType();
if (type == null) {
return -1;
} else {
switch (type) {
case STRING:
return cellData.getStringValue().getBytes().length;
case BOOLEAN:
return cellData.getBooleanValue().toString().getBytes().length;
case NUMBER:
return cellData.getNumberValue().toString().getBytes().length;
default:
return -1;
}
}
}
}
}
3. 工具类
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
/**
* 导出工具类
*
* @author Windows
*/
public class ExcelUtils {
/**
* 导出公共方法
*
* @param datas 要导出的数据集
* @param clazz 导出的实体
* @param fileName 导出文件名称
* @throws IOException
*/
public static void export(List datas, @Nullable Class<?> clazz, String fileName) throws IOException {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = sra.getResponse();
response.setContentType("application/vnd.ms-excel;charset=utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyExcel没有关系
//response.setContentType("application/json;charset=utf-8");
//response.setCharacterEncoding("utf-8");
// 设置文件名
String exportFileName = URLEncoder.encode(fileName + DateUtil.today(), "UTF-8");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + exportFileName + ExcelTypeEnum.XLSX.getValue());
// 创建一个写出的单元格样式对象
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 设置水平对齐方式
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 设置填充前景色
headWriteCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
// 创建写出Excel的字体对象
WriteFont headWriteFont = new WriteFont();
//设置字体高度
// headWriteFont.setFontHeightInPoints((short) 10);
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setWrapped(true);
// 创建一个写出的单元格样式对象
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
//设置内容靠中对齐
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 边框设置
// contentWriteCellStyle.setBorderTop(BorderStyle.THIN); // 设置单元格上边框为细线
// contentWriteCellStyle.setBorderBottom(BorderStyle.THICK); // 设置单元格下边框为粗线
// contentWriteCellStyle.setBorderLeft(BorderStyle.MEDIUM); // 设置单元格左边框为中线
// contentWriteCellStyle.setBorderRight(BorderStyle.MEDIUM_DASHED); // 设置单元格右边框为中虚线
// 设置行样式
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 设置自定义自适应列宽样式
ExcelWidthStyleStrategy excelWidthStyleStrategy = new ExcelWidthStyleStrategy();
// 如果不用模板的方式导出的话,是doWrite
EasyExcel.write(response.getOutputStream(), clazz)
.excelType(ExcelTypeEnum.XLSX)
// .password("123456")
.autoCloseStream(true)
.registerWriteHandler(horizontalCellStyleStrategy)
// .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(excelWidthStyleStrategy)
.sheet(fileName) // 设置sheet名称
.doWrite(datas);
}
/**
* 根据Excel模板,批量导入数据
*
* @param file 导入的Excel
* @param clazz 解析的类型
* @return 解析完成的数据
*/
public static List<?> importExcel(MultipartFile file, Class<?> clazz) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("没有文件或者文件内容为空!");
}
List<?> dataList;
BufferedInputStream ipt;
try {
InputStream is = file.getInputStream();
// 用缓冲流对数据流进行包装
ipt = new BufferedInputStream(is);
// 数据解析监听器
ExcelListener<?> listener = new ExcelListener<>();
// 读取数据
EasyExcel.read(ipt, clazz, listener).sheet().doRead();
// 获取去读完成之后的数据
dataList = listener.getList();
} catch (Exception e) {
throw new RuntimeException("数据导入失败!" + e);
}
return dataList;
}
}
4. 在实体类上添加注解
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 测试实体类
* 使用注解单独给字段设置宽度会覆盖自定义宽度策略
*
* @author ddz
*/
@Data
@AllArgsConstructor
//@ColumnWidth(value = 50) // 宽
public class User1 implements Serializable {
@ExcelProperty(value = "姓名")
// @ColumnWidth(value = 20)
private String name;
@ExcelProperty(value = "年龄")
// @ColumnWidth(value = 50)
private Integer age;
@ExcelProperty(value = "生日")
// @ColumnWidth(value = 100)
@DateTimeFormat(value = "YYYY-MM-dd")
private Date birthday;
@ExcelProperty(value = "性别")
// @ColumnWidth(50)
private Integer gender;
@ExcelProperty(value = "余额")
@NumberFormat(value = "¥0.000")
private Double balance;
}
5. 导出Excel
@GetMapping("export")
public void export() throws IOException {
// 这里我使用了假数据
List<User1> users = new ArrayList<>();
users.add(new User1("张三", 18, DateUtil.date(), 1,11.12));
users.add(new User1("李四", 22, DateUtil.date(), 0,11D));
users.add(new User1("王五", 26, DateUtil.date(), 0,11.121212));
ExportUtils.export(users, User1.class, "自定义文件名");
}
6. 导入Excel
1. 添加导入监听类
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Excel数据解析监听器, 数据解析方法异步执行
*
* @param <T> Excel中数据的类型
* @author Windows
*/
@Getter
@Setter
@NoArgsConstructor
public class ExcelListener<T> extends AnalysisEventListener<T> {
// 加入一个判断标签,判断数据是否已经读取完
private volatile boolean retryLock = false;
// 解析完成后的数据集合, 监听对象初始化之后,立即初始化集合对象
private final List<T> dataList = new ArrayList<>();
// 每次最多导入条数
private final int batchSize = 2000;
/**
* 获取解析后的数据集合, 如果数据还没有被解析完成,会对读取该集合的线程进行阻塞,直到数据读取完成之后,进行解锁。
* 如果一次导入数据超过batchSize条,则以抛异常的形式阻止导入数据
*
* @return 解析后的数据集合
*/
public List<T> getList() {
while (true) {
if (retryLock) {
if (dataList.size() > batchSize) {
// 手动清空数据内存数据,减少内存消耗
dataList.clear();
throw new RuntimeException("一次最多导入" + batchSize + "条数据");
} else {
return dataList;
}
}
}
}
/**
* Excel每解析一行数据,就会调用一次该方法
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context analysis context
*/
@Override
public void invoke(T data, AnalysisContext context) {
dataList.add(data);
}
/**
* 读取表头内容
*
* @param headMap 表头部数据
* @param context 数据解析上下文
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
// 在此处可以进行表头的校验,headMap对应的就是表头的数据
// 在此处有个很奇怪的地方,就是当用户导入的文件没有任何表头的时候,也就是表头这一行为空
// 表头检验则不会进行这个函数,且数据导入成功,所以这个地方要确认清楚
// 最后我是在拿到所有数据,包括表头,再取第一行为表头进行校验
//System.out.println("表头:" + headMap);
}
/**
* 流中的数据解析完成之后,就会调用此方法
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 数据解析完成,解锁
retryLock = true;
}
/**
* 解析过程如果发生异常,会调用此方法
*/
@Override
public void onException(Exception exception, AnalysisContext context) {
throw new RuntimeException("Excel数据异常,请检查或联系管理员!");
}
}
2. 导入Excel
@PostMapping("importExcel")
public void importExcel(MultipartFile file) {
List<?> objects = ExportUtils.importExcel(file, User1.class);
System.out.println(objects);
}
GitHub 加速计划 / ea / easyexcel
31.64 K
7.47 K
下载
快速、简洁、解决大文件内存溢出的java处理Excel工具
最近提交(Master分支:3 个月前 )
c42183df
Bugfix 3 个月前
efa7dff6 * 重新加回 `commons-io`
3 个月前
更多推荐
已为社区贡献31条内容
所有评论(0)