一、简单使用
引入依赖:
这里我们可以使用最新的4.0.2
版本,也可以选择之前的稳定版本,3.1.x
以后的版本API
大致相同,新的版本也会向前兼容(3.1.x
之前的版本,部分API
可能在高版本被废弃),关于POI、JDK
版本适配问题,具体可参考官网-版本说明。
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.2</version></dependency>
下载excel文件:
java"> @GetMapping("/download")public void excelDownload(HttpServletResponse response) throws IOException {response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");EasyExcel.write(response.getOutputStream(), Data.class).sheet("模板").doWrite(datas);String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");}
读取excel文件:
java"> @PostMapping("/read")public void read(MultipartFile file) throws IOException {1、这只是简单演示,一般不使用 doReadSync 方法,此方法同步执行的,即它会阻塞当前线程,直到读取完整个Excel文件并返回所有数据。读取大型文件时,可能会导致程序响应变慢或阻塞。2、使用head映射字段时,该实体类上不能加 @Accessors 注解,加上此注解会字段映射不成功。3、一般会使用监听器 + doRead 方法实现excel文件的读取List<Data> datas = EasyExcel.read(file.getInputStream()).sheet().head(Data.class).doReadSync();System.out.println(datas);}
二、常用注解
1、@ExcelProperty注解
这个注解应该是最常用的注解,通常用来映射字段跟excel的列名,有以下几个属性:
名称 | 默认值 | 描述 |
---|---|---|
value | 空 | 用于匹配excel中的头,必须全匹配,如果有多行头,会匹配最后一行头 |
order | Integer.MAX_VALUE | 优先级高于value ,会根据order 的顺序来匹配实体和excel中数据的顺序 |
index | -1 | 优先级高于value 和order ,会根据index 直接指定到excel中具体的哪一列 |
converter | 自动选择 | 指定当前字段用什么转换器,默认会自动选择。写的情况下只要实现com.alibaba.excel.converters.Converter#convertToExcelData(com.alibaba.excel.converters.WriteConverterContext<T>) 方法即可 |
注意:
1、如果没有特殊的调整一般,使用value属性就够了,在读取或者导出时都能匹配或者映射为对应的列名。
2、value 跟 index 可以在导出数据的时候配合使用,value指定列名,index指定该列的顺序,例如:@ExcelProperty(value = "性别",index = 3) 代表列名为 性别,导出到第三列的位置。但是在导入时,如果设置了order属性,表示会根据指定列来匹配字段,例如上面就会将第三列匹配为性别字段,如果该列字段为空,或者字段类型不匹配就会报错,一般在读取数据时不会这么使用这个属性。3、order 属性代表按顺序匹配,比如说导出数据时,会按照字段上该属性的顺序,依次为列设置对应字段的值,比如order最小的,就匹配第一列的值,依次往后,在导出时也是一样,order最小的值,导出到第一列依次往后。
4、converter:自定义的类型转换器,该属性可以实现自定义处理类,这个功能通常用来在 Excel 数据与 Java 对象之间进行特定格式的转换,例如日期、布尔值、自定义对象等。
实现
Converter
接口
要自定义一个转换器,需要实现 EasyExcel 提供的Converter
接口。重写必要的方法
supportJavaTypeKey()
: 指定支持的 Java 数据类型。convertToExcelData()
: 将 Java 数据类型转换为 Excel 单元格数据。convertToJavaData()
: 将 Excel 单元格数据转换为 Java 数据类型EasyExcel 自带了一些常用的转换器(例如
LocalDateConverter
、IntegerConverter
等),可以直接使用而无需自定义。
例如:在姓名上加上该属性:
java">@ExcelProperty(value = "姓名",converter = ExcelStringConverter.class)
private String name;
实现自定义处理类:
java">public class ExcelStringConverter implements Converter<String> {@Overridepublic Class<?> supportJavaTypeKey() {return String.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}@Overridepublic String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {return cellData.getStringValue() + "导入数据进行处理!";}@Overridepublic WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {return new WriteCellData<>(value + "导出数据进行处理");}}
例如实现在 Excel 中用 "是"
和 "否"
表示布尔值,而不是默认的 true/false
。
java">public class BooleanStringConverter implements Converter<Boolean> {@Overridepublic Class<?> supportJavaTypeKey() {return Boolean.class; // 支持的 Java 类型}@Overridepublic WriteCellData<?> convertToExcelData(Boolean value, ExcelContentProperty contentProperty) {return new WriteCellData<>(value ? "是" : "否"); // 将布尔值转为字符串}@Overridepublic Boolean convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty) {String stringValue = cellData.getStringValue();return "是".equals(stringValue); // 将字符串 "是"/"否" 转为布尔值}
}
2、@ExcelIgnore注解
- 作用范围:数据实体类的字段上;
- 注解释义:当前字段不参与excel列的匹配,即处理时忽略该字段;
在默认情况下,数据模型类中的所有字段都会参与匹配,可如果你定义的Java类中,有些字段在读写时并不需要参与进来,这时就可以给对应字段加上@ExcelIgnore
注解,具备该注解的字段会被忽略。
3、@ExcelIgnoreUnannotated注解
- 作用范围:数据模型类上;
- 注解释义:匹配列时忽略所有未使用
@ExcelProperty
注解的字段;
如果类中许多字段都不想参与excel
读写,而你又嫌挨个加@ExcelIgnore
注解麻烦,这时就可以直接在类上加一个@ExcelIgnoreUnannotated
注解,以此来忽略所有未添加@ExcelProperty
注解的字段。
4、@DateTimeFormat注解
- 作用范围:数据实体类的字段上;
- 注解释义:用
String
接收日期数据时,会根据指定格式转换日期; - 可选参数:
value
:日期数据转换为字符串的目标格式;use1904windowing
:excel日期数据默认从1900
年开始,但有些会从1904
开始;
在解析excel
文件时,如果使用String
字段接收日期数据,就会根据指定的格式转换数据,格式可以参考java.text.SimpleDateFormat
的写法,例如yyyy-MM-dd HH:mm:ss
。而在往excel
写数据时,如果Java中的字段类型为Date、LocalDate、LocalDateTime
等日期类型,则会将日期数据转换为指定的格式写入对应列。
例如:
java"> @DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")
5、@NumberFormat注解
- 作用范围:数据实体类的字段上;
- 注解释义:用
String
接收数值数据时,会根据指定格式转换数值; - 可选参数:
value
:数值转换为字符串的目标格式;roundingMode
:数值格式化时的舍入模式,如四舍五入、向上取整等;
这个注解和前一个注解类似,只不过是用于将非整数类型的数值数据转换成给定格式,格式可以参考java.text.DecimalFormat
的写法,如#.##
。除了可以指定格式外,还可以指定舍入模式,枚举可参考java.math.RoundingMode
类。
使用方法:
-
指定数字格式
使用@NumberFormat
注解的value
属性指定数字格式。例如:#
: 表示一个数字字符(整数部分)。0
: 表示一个数字字符(小数部分,不足补零)。,
: 表示千分位分隔符。.
: 表示小数点。
-
与
@ExcelProperty
搭配使用
在数值类型字段上添加@NumberFormat
,并用@ExcelProperty
指定列名。
java"> @ExcelProperty("销售金额")@NumberFormat("#,##0.00") // 指定数字格式,保留两位小数,带千分位private BigDecimal salesAmount;
三、常用生成注解
1、@ColumnWidth注解
- 作用范围:数据模型类上、字段上;
- 注解释义:设置列的宽度;
这个注解如果加在类上,则会对所有字段生效;如果单独加在某个字段上,则只对特定的列有效,单位是px
。
例如:
java"> @ExcelProperty("销售金额")@ColumnWidth(200)private BigDecimal salesAmount;
2、@ContentFontStyle注解
- 作用范围:数据模型类上、字段上;
- 注解释义:用于设置单元格内容字体格式的注解;
- 可选参数:
fontName
:字体名称,如“黑体、宋体、Arial”等;fontHeightInPoints
:字体高度,以磅为单位;italic
:是否设置斜体(字体倾斜);strikeout
:是否设置删除线;color
:字体的颜色,通过RGB
值来设置;typeOffset
:偏移量,用于调整字体的位置;underline
:是否添加下划线;bold
:是否对字体加粗;charset
:设置编码格式,只能对全局生效(字段上设置无效)。
这个注解用于设置主体内容的字体样式(不包含表头),与上个注解同理,加在类上对整个excel
文件生效,加在字段上只对单列有效,可以通过该注解来设置字体风格、高度、是否斜体等属性。
java"> @ExcelProperty(value ="日期")@DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")@ContentFontStyle(fontName = "黑体",/* 字体类型 */fontHeightInPoints = 50, /* 字体高度,以磅为单位; */italic = BooleanEnum.TRUE,/* 是否设置斜体(字体倾斜); */strikeout = BooleanEnum.TRUE,/* 是否设置删除线; */color = 14,/* 字体的颜色,通过RGB值来设置; 0 黑色 (默认)9 红色10 绿色12 蓝色13 黄色14 粉色15 青色16 白色 */typeOffset = 1,/*偏移量,用于调整字体的位置; */underline = 1,/* 是否添加下划线; */bold = BooleanEnum.TRUE/* 是否对字体加粗; */)private LocalDateTime date;
3、@ContentRowHeight注解
- 作用范围:数据模型类上;
- 注解释义:用于设置行高。
这个注解只能加在类上面,作用就是设置单元格的高度,但这里不能像Excel
那样精准设置不同行的高度,只能设置所有单元格统一的高度。
java">@ContentRowHeight(80)
4、@ContentStyle注解
- 作用范围:数据模型类上、字段上;
- 注解释义:用于设置内容格式;
属性名 | 类型 | 功能描述 |
horizontalAlignment | HorizontalAlignment | 设置单元格内容的水平对齐方式(如左对齐、居中、右对齐)。 |
verticalAlignment | VerticalAlignment | 设置单元格内容的垂直对齐方式(如顶部对齐、中间对齐、底部对齐)。 |
wrapped | boolean | 是否自动换行(true 开启自动换行)。 |
dataFormat | short | 设置单元格的数据格式(例如日期格式、数字格式等)。 |
fillPatternType | FillPatternType | 设置单元格的填充模式(如纯色填充、斜线填充等)。 |
fillForegroundColor | short | 设置单元格的前景色(通过颜色索引表示)。 |
fillBackgroundColor | short | 设置单元格的背景色(通过颜色索引表示)。 |
borderLeft | BorderStyle | 设置单元格左边框样式(如实线、虚线等)。 |
borderRight | BorderStyle | 设置单元格右边框样式。 |
borderTop | BorderStyle | 设置单元格顶部边框样式。 |
borderBottom | BorderStyle | 设置单元格底部边框样式。 |
leftBorderColor | short | 设置单元格左边框颜色。 |
rightBorderColor | short | 设置单元格右边框颜色。 |
topBorderColor | short | 设置单元格顶部边框颜色。 |
bottomBorderColor | short | 设置单元格底部边框颜色。 |
java"> @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER,/* 水平对齐方式,如居中、左对齐等; */verticalAlignment = VerticalAlignmentEnum.CENTER,/*垂直对齐方式,如上对齐、下对齐等;*/wrapped = BooleanEnum.TRUE, /* 设置文本是否应该换行(自动根据内容长度换行); */dataFormat = 0, /*数据格式,对应excel的内置数据格式; */fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND,/*设置单元格的填充模式(如纯色填充、斜线填充等)*/fillForegroundColor = 9,/*设置单元格的前景色(通过颜色索引表示)。*/fillBackgroundColor = 12,/*设置单元格的背景色(通过颜色索引表示)。*/borderLeft = BorderStyleEnum.THICK,/*设置单元格左边框样式(如实线、虚线等)。*/borderRight = BorderStyleEnum.THICK,/*设置单元格右边框样式。*/borderTop = BorderStyleEnum.THICK,/*设置单元格顶部边框样式。*/borderBottom = BorderStyleEnum.THICK,/*设置单元格底部边框样式。*/leftBorderColor = 14,/*设置单元格左边框颜色。*/rightBorderColor = 14,/*设置单元格右边框颜色。*/topBorderColor = 14,/*设置单元格顶部边框颜色。*/bottomBorderColor = 14/*设置单元格底部边框颜色。*/)
这个注解的属性还有很多,需要的话可以自行再查阅。
5、@HeadFontStyle注解
- 作用范围:数据模型类上、字段上;
- 注解释义:用于定制标题字体格式。
这个注解的作用和可选参数,与@ContentFontStyle
注解类似,不过这个注解是针对列头(表头)有效罢了。
- 可选参数:
fontName:
字体名称,例如Arial
、宋体
等。- fontHeightInPoints:字体大小,以磅为单位.
- bold:是否加粗。
- color:字体颜色,使用 Excel 的内置颜色索引值。
6、@HeadRowHeight注解
- 作用范围:数据模型类上;
- 注解释义:用于设置标题行的行高。
此注解的作用参考@ContentRowHeight
注解,当前注解只对表头生效。
java">@HeadRowHeight(30) // 设置表头行高为 30pt
7、@HeadStyle注解
- 作用范围:数据模型类上
- 注解释义:用于设置标题样式。
该注解的作用和可选参数参考@
ContentStyle注解,但是当前注解只对表头生效。
8、@OnceAbsoluteMerge注解
- 作用范围:数据模型类上;
- 注解释义:用于合并指定的单元格;
- 可选参数:
firstRowIndex
:从哪行开始合并;lastRowIndex
:到哪行结束合并;firstColumnIndex
:从哪列开始合并;lastColumnIndex
:到哪列结束合并。
从这个注解提供的可选参数就能看出具体作用,这是通过单元格行、列索引的方式,指定生成excel
文件时要合并的区域。不过要注意,使用该注解只能合并一次(对应OnceAbsoluteMerge
这个合并策略类)。
java">@OnceAbsoluteMerge(firstRowIndex = 0, lastRowIndex = 1, firstColumnIndex = 0, lastColumnIndex = 1)
9、@ContentLoopMerge注解
- 作用范围:数据模型类的字段上;
- 注解释义:用于合并单元格;
- 可选参数:
eachRow
:指定每x
行合并为一行;columnExtend
:指定每x
列合并为一列。
该注解也是用于合并单元格的,但是可以合并多次,不过只能实现每隔n
个单元格合并,使用起来限制很大,通常也不会选择通过这种注解的形式来合并单元格,这里了解即可。
四、常用读取Excel方法:
1、同步读取所有数据后返回
同步的返回,不推荐使用,如果数据量大会把数据放到内存里面,会影响性能。这里只做简单得举例:
java"> @PostMapping("/import")public void importExcel(MultipartFile file) throws IOException {// head: 指定读用哪个class去读// headRowNumber: 指定行头,如果行头指定错误可能会读取不到数据。如果多行头,可以设置其他值。没有指定头,也就是默认是第1行。// sheet:指定读哪个sheet页,从0开始,第一个sheet是0,第二个是1,默认就是读第一个// doReadSync: 同步读,读取完所有数据返回List<ExcelDemo> list = EasyExcel.read(file.getInputStream()).headRowNumber(1).head(ExcelDemo.class).sheet().doReadSync();for (ExcelDemo data : list) {log.info("读取到数据:{}", JSON.toJSONString(data));}}
2、使用监听器读取所有数据
监听器是EasyExcel常用的一个方法,监听器的好处就是可以一行一行获取数据,不用全部读完在进行处理。
监听器示例:
java">@Slf4j
public class ExcelDemoListener extends AnalysisEventListener<ExcelDemo> {private final List<ExcelDemo> data = new ArrayList<>();/*** 每解析一条数据都会触发一次invoke()方法*/@Overridepublic void invoke(ExcelDemo excelDemo, AnalysisContext analysisContext) {log.info("成功解析到一条数据:{}", excelDemo);data.add(excelDemo);}/*** 当一个excel文件所有数据解析完成后,会触发此方法*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {log.info("所有数据都已解析完毕!");}public List<ExcelDemo> getData() {return data;}
}
使用监听器读取excel文件:
java"> @PostMapping("/import")public void importExcel(MultipartFile file) throws IOException {// 监听器需要手动new出来ExcelDemoListener excelDemoListener = new ExcelDemoListener();// 将监听器放入read方法中,会走监听器内部的方法来读取数据 EasyExcel.read(file.getInputStream(),ExcelDemo.class,excelDemoListener).sheet().doRead();List<ExcelDemo> data = excelDemoListener.getData();log.info("总共解析到到" + data.size() + "条数据!");}
监听器内部逻辑可以自行定义,一般会设置数据上限,当数据读取到上限时,就自动批量存储到数据库中。
3、读多个sheet
一次读取全部sheet,需要使用 doReadAll 方法,这个方法一次读取全部sheet页,并传给监听器处理。这中适用于全部sheet页全部都是同一个实体类接收。
java"> ExcelDemoListener excelDemoListener = new ExcelDemoListener();EasyExcel.read(file.getInputStream(),ExcelDemo.class,excelDemoListener).doReadAll();List<ExcelDemo> data = excelDemoListener.getData();log.info("总共解析到到" + data.size() + "条数据!");
读取指定的sheet页,并指定不同的实体类接收。
java"> try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) {// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的ListenerExcelDemoListener excelDemoListener = new ExcelDemoListener();ReadSheet readSheet1 =EasyExcel.readSheet(0).head(ExcelDemo.class).registerReadListener(excelDemoListener).build();ReadSheet readSheet2 =EasyExcel.readSheet(1).head(ExcelDemo.class).registerReadListener(excelDemoListener).build();// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能excelReader.read(readSheet1, readSheet2);}
4、日期、数字或者自定义格式转换
需要用到上面的注解,以及自定义转换器实现类。
5、多行头
当读取excel时,如果行头并不是第一行,就需要配合 headRowNumber 方法指定行头是哪一行,但是如果指定的不对,会导致数据读取失败。
java"> ExcelDemoListener excelDemoListener = new ExcelDemoListener();EasyExcel.read(file.getInputStream(),ExcelDemo.class,excelDemoListener).sheet()// 默认读取第一行为表头,如果第一行不是,需要单独设置headRowNumber,从0开始.headRowNumber(1).doRead();List<ExcelDemo> data = excelDemoListener.getData();log.info("总共解析到到" + data.size() + "条数据!");
6、读取表头数据
只需要在监听器中实现一个方法,只要重写invokeHeadMap方法即可
java"> @Overridepublic void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));}@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));// 如果想转成成 Map<Integer,String>// 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener// 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换}
解析到一条头数据:{0:{"columnIndex":0,"dataFormatData":{"format":"General","index":0},"rowIndex":0,"stringValue":"姓名","type":"STRING"},1:{"columnIndex":1,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"日期","type":"STRING"},2:{"columnIndex":2,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"年龄","type":"STRING"},3:{"columnIndex":3,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"薪水","type":"STRING"},4:{"columnIndex":4,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"地址","type":"STRING"}}
成功解析到一条数据:ExcelDemo(name=张三, age=18, salary=11111.11, address=北京, dateTime=2024-12-21T20:20:20)
7、额外信息(批注、超链接、合并单元格信息读取)
一般很少用,需要可以再了解一下,这里只简单做示例:
需要 在监听器里面多实现一个 extra
方法:
java"> @Overridepublic void extra(CellExtra extra, AnalysisContext context) {log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));switch (extra.getType()) {case COMMENT:log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(),extra.getText());break;case HYPERLINK:if ("Sheet1!A1".equals(extra.getText())) {log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(),extra.getColumnIndex(), extra.getText());} else if ("Sheet2!A1".equals(extra.getText())) {log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{},"+ "内容是:{}",extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),extra.getLastColumnIndex(), extra.getText());} else {Assert.fail("Unknown hyperlink!");}break;case MERGE:log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),extra.getLastColumnIndex());break;default:}}
java"> // 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener())// 需要读取批注 默认不读取.extraRead(CellExtraTypeEnum.COMMENT)// 需要读取超链接 默认不读取.extraRead(CellExtraTypeEnum.HYPERLINK)// 需要读取合并单元格信息 默认不读取.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();
8、读取公式和单元格类型
也比较少用,需要自行钻研,只简单举例:
java">@Getter
@Setter
@EqualsAndHashCode
public class CellDataReadDemoData {private CellData<String> string;// 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是numberprivate CellData<Date> date;private CellData<Double> doubleData;// 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复private CellData<String> formulaValue;
}
java"> @Testpublic void cellDataRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "cellDataDemo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, CellDataReadDemoData.class, new CellDataDemoHeadDataListener()).sheet().doRead();}
9、数据转换等异常处理
需要在监听器里面实现重写onException方法即可:
java"> @Overridepublic void onException(Exception exception, AnalysisContext context) {log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());// 如果是某一个单元格的转换异常 能获取到具体行号// 如果要获取头的信息 配合invokeHeadMap使用if (exception instanceof ExcelDataConvertException) {ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());}}
10、不创建对象的读
不创建对象的读,可以接收一个map集合,然后在监听器中自行进行转换。
java">@Slf4j
public class DataListener extends AnalysisEventListener<Map<Integer, String>> {/*** 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收*/private static final int BATCH_COUNT = 5;private List<Map<Integer, String>> dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {log.info("解析到一条数据:{}", JSON.toJSONString(data));dataList.add(data);if (dataList.size() >= BATCH_COUNT) {saveData();dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {saveData();log.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {log.info("{}条数据,开始存储数据库!", dataList.size());log.info("存储数据库成功!");}
}
java"> // 这里 只要,然后读取第一个sheet 同步读取会自动finishEasyExcel.read(fileName, new DataListener()).sheet().doRead();
五、常用导出Excel方法
1、简单导出
注意:简单导出 在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入
java"> @PostMapping("/export")public void exportExcel(HttpServletResponse response) throws IOException {// write 指定输出流,跟模板对象// sheet 指定导出sheet页名称// dowrite 指定数据源EasyExcel.write(response.getOutputStream(), ExcelDemo.class).sheet("excel模板").doWrite(this::getDatas);response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");}
2、根据参数只导出指定列
根据参数名列表,导出指定字段,需要使用 includeColumnFieldNames 方法:
java"> //导出指定字段Set<String> exportFields = new HashSet<String>();exportFields.add("name");exportFields.add("age");// write 指定输出流,跟模板对象// sheet 指定导出sheet页名称// dowrite 指定数据源EasyExcel.write(response.getOutputStream(), ExcelDemo.class).includeColumnFieldNames(exportFields).sheet("excel模板").doWrite(getDatas());response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");
根据参数名列表,忽略指定字段,需要使用 excludeColumnFiledNames 方法:
java"> //导出指定字段Set<String> noExportFields = new HashSet<String>();noExportFields.add("name");noExportFields.add("age");// write 指定输出流,跟模板对象// sheet 指定导出sheet页名称// dowrite 指定数据源EasyExcel.write(response.getOutputStream(), ExcelDemo.class).excludeColumnFieldNames(noExportFields).sheet("excel模板").doWrite(getDatas());response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");
导出指定列的数据还可以结合 @ExcelIgnore注解 或 @ExcelIgnoreUnannotated注解 使用,来导出指定列,或者忽略某些列。
3、复杂头写入
使用@ExcelProperty注解即可设置:
java">@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExcelDemo {@ExcelProperty({"表头1","姓名"})private String name;@ExcelProperty({"表头1","年龄"})private int age;@ExcelProperty({"薪水"})private BigDecimal salary;@ExcelProperty({"表头2","地址"})private String address;@ExcelProperty({"表头2","日期"})private LocalDateTime dateTime;}
相同表头会合并。
4、重复多次写入(写到单个或者多个Sheet)
重复写入同一个sheet页:
java"> // 方法1: 如果写到同一个sheet// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExcelDemo.class).build()) {// 这里注意 如果同一个sheet只要创建一次WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来for (int i = 0; i < 5; i++) {// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<ExcelDemo> data = getDatas();excelWriter.write(data, writeSheet);}}response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");
写入不同得sheet页,同一个对象:
java"> // 方法2: 如果写到不同的sheet 同一个对象// 这里 指定文件try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExcelDemo.class).build()) {// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面for (int i = 0; i < 5; i++) {// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<ExcelDemo> data = getDatas();excelWriter.write(data, writeSheet);}}
写入不同的sheet页,不同的对象:
java"> // 方法3 如果写到不同的sheet 不同的对象// 这里 指定文件try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build()) {// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面for (int i = 0; i < 5; i++) {// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class// 实际上可以一直变WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(ExcelDemo.class).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<ExcelDemo> data = getDatas();excelWriter.write(data, writeSheet);}}
5、日期、数字或者自定义格式转换
日期、数字、自定义格式转行,可以结合 注解:
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@NumberFormat("#.##%")
Converter 自定义转换器
上面写了,不再举例
6、图片导出
7、超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式
8、根据模板写入
我得理解就是,在已经提供了一份excel文件中,继续导入数据,例如我们现在有一份excel文件如下所示:
现在我们要以这个文件为模板,在这个文件得基础上继续导出数据,会得到如下所示:
导出代码:
java"> String templateFileName = "C:\\Users\\Administrator\\Desktop\\ces\\" + "response111.xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM// 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入EasyExcel.write(response.getOutputStream(), ExcelDemo.class).withTemplate(templateFileName).sheet().doWrite(getDatas());
9、列宽、行高
需要结合注解:
@ContentRowHeight(10) :设置行高为10px
@HeadRowHeight(20):设置标题行的行高为20px
@ColumnWidth(25):列的宽度为25px
java">@Data
@AllArgsConstructor
@NoArgsConstructor
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class ExcelDemo {@ExcelProperty({"表头1","姓名"})private String name;@ExcelProperty({"表头1","年龄"})private int age;@ExcelProperty({"薪水"})private BigDecimal salary;@ExcelProperty({"表头2","地址"})private String address;@ExcelProperty({"表头2","日期"})private LocalDateTime dateTime;
}
10、注解形式自定义样式
需要结合上面讲述得注解:
java">@Data
@AllArgsConstructor
@NoArgsConstructor
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成20
@HeadFontStyle(fontHeightInPoints = 20)
// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成20
@ContentFontStyle(fontHeightInPoints = 20)
public class ExcelDemo {// 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14)// 字符串的头字体设置成20@HeadFontStyle(fontHeightInPoints = 30)// 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40)// 字符串的内容字体设置成20@ContentFontStyle(fontHeightInPoints = 30)@ExcelProperty({"表头1","姓名"})private String name;@ExcelProperty({"表头1","年龄"})private int age;@ExcelProperty({"薪水"})private BigDecimal salary;@ExcelProperty({"表头2","地址"})private String address;@ExcelProperty({"表头2","日期"})private LocalDateTime dateTime;}
一般只会设置一些简单样式,具体需要可以自行查询相应注解。
11、使用内置策略模式设置自定义样式
EasyExcel中提供了两个样式策略,用来是设置导出文件得样式,只需要简单配置即可使用:
HorizontalCellStyleStrategy:允许设置每一行的样式一致,或者隔行样式一致。
AbstractVerticalCellStyleStrategy:使用这个策略时,可以为每一列单独设置样式,需要通过回调函数来定义不同列的样式。
设置每一行得样式:
HorizontalCellStyleStrategy 方法,一般接收两个 WriteCellStyle 类型得参数,第一个是设置表头格式得,第二个是设置内容格式的。
WriteCellStyle 是 EasyExcel 中用于设置 Excel 单元格样式的类。每个属性用于定义单元格的不同样式,包括字体、边框、填充颜色、对齐方式等。下面是对每个参数详细得简单的讲解:
java"> WriteCellStyle cellStyle = new WriteCellStyle();// 1. DataFormatData 设置数据格式// 这里假设你设置为数字格式, 示例代码中未使用具体的 DataFormatData// cellStyle.setDataFormatData(new DataFormatData("0.00"));// 2. WriteFont 设置字体WriteFont font = new WriteFont();font.setFontHeightInPoints((short) 12); // 字体大小为 12font.setBold(true); // 设置加粗font.setFontName("Arial"); // 字体设置为 ArialcellStyle.setWriteFont(font);// 3. hidden 设置隐藏cellStyle.setHidden(false); // 设置为可见// 4. locked 设置是否锁定cellStyle.setLocked(true); // 设置单元格为锁定,不能编辑// 5. quotePrefix 设置前缀引号cellStyle.setQuotePrefix(true); // 设置文本前加引号// 6. HorizontalAlignment 设置水平对齐cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 设置为居中对齐// 7. wrapped 设置是否换行cellStyle.setWrapped(true); // 设置文本自动换行// 8. VerticalAlignment 设置垂直对齐cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 设置为垂直居中// 9. rotation 设置文本旋转cellStyle.setRotation((short) 90); // 设置文本旋转 90 度// 10. indent 设置缩进cellStyle.setIndent((short) 2); // 设置缩进为 2 个字符// 11-14. BorderStyle 设置边框样式cellStyle.setBorderLeft(BorderStyle.THIN); // 设置左边框为细边框cellStyle.setBorderRight(BorderStyle.MEDIUM); // 设置右边框为中等边框cellStyle.setBorderTop(BorderStyle.THICK); // 设置顶部边框为粗边框cellStyle.setBorderBottom(BorderStyle.DOTTED); // 设置底部边框为虚线// 15-18. 边框颜色设置cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex()); // 左边框颜色为黑色cellStyle.setRightBorderColor(IndexedColors.BLUE.getIndex()); // 右边框颜色为蓝色cellStyle.setTopBorderColor(IndexedColors.GREEN.getIndex()); // 顶部边框颜色为绿色cellStyle.setBottomBorderColor(IndexedColors.RED.getIndex()); // 底部边框颜色为红色// 19. FillPatternType 设置填充模式cellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); // 设置为实心填充模式// 20-21. 背景颜色和前景颜色设置cellStyle.setFillBackgroundColor(IndexedColors.YELLOW.getIndex()); // 设置背景色为黄色cellStyle.setFillForegroundColor(IndexedColors.ORANGE.getIndex()); // 设置前景色为橙色// 22. shrinkToFit 设置是否自适应缩小cellStyle.setShrinkToFit(true); // 设置文本自适应缩小
简单使用:
java"> //行头字体WriteFont headFont = new WriteFont();headFont.setFontName("华文楷体");headFont.setFontHeightInPoints((short) 18);headFont.setBold(true);//内容字体WriteCellStyle cellStyle = createCellStyle();WriteFont contentFont = new WriteFont();contentFont.setFontName("宋体");contentFont.setFontHeightInPoints((short) 10);contentFont.setBold(false);//行头设置WriteCellStyle headCellStyle = new WriteCellStyle();headCellStyle.setWriteFont(headFont);headCellStyle.setFillForegroundColor(IndexedColors.WHITE1.getIndex());headCellStyle.setBorderTop(BorderStyle.THIN);headCellStyle.setBorderBottom(BorderStyle.THIN);headCellStyle.setBorderLeft(BorderStyle.THIN);headCellStyle.setBorderRight(BorderStyle.THIN);headCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//内容设置WriteCellStyle contentCellStyle = new WriteCellStyle();contentCellStyle.setWriteFont(contentFont);contentCellStyle.setBorderTop(BorderStyle.THIN);contentCellStyle.setBorderBottom(BorderStyle.THIN);contentCellStyle.setBorderLeft(BorderStyle.THIN);contentCellStyle.setBorderRight(BorderStyle.THIN);contentCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headCellStyle, cellStyle);EasyExcel.write(response.getOutputStream(), ExcelDemo.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板").doWrite(getDatas());
设置列的样式:
AbstractVerticalCellStyleStrategy
是一个抽象类,必须通过继承并重写方法来实现自定义的列样式。主要是重写下面两个方法:
headCellStyle(Head head) ----设置标题栏的列样式
contentCellStyle(Head head) ----设置表格内容的列样式
具体实现代码:
先实现重写AbstractVerticalCellStyleStrategy
:
java">public class CustomVerticalCellStyleStrategy extends AbstractVerticalCellStyleStrategy {// 重写定义表头样式的方法@Overrideprotected WriteCellStyle headCellStyle(Head head) {WriteCellStyle writeCellStyle = new WriteCellStyle();if(head.getColumnIndex() == 0) {//设置行头的第一列 单元格水平居中writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);}else if(head.getColumnIndex() == 1){//设置行头的第二列 单元格水平居左writeCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);}else if(head.getColumnIndex() == 2){//设置行头的第三列 单元格水平居右writeCellStyle.setHorizontalAlignment(HorizontalAlignment.RIGHT);}else if(head.getColumnIndex() == 3){//设置行头大于第三列 单元格水平居右writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);}return writeCellStyle;}// 重写定义内容部分样式的方法@Overrideprotected WriteCellStyle contentCellStyle(Head head) {WriteCellStyle writeCellStyle = new WriteCellStyle();writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);writeCellStyle.setFillBackgroundColor(IndexedColors.GREEN.getIndex());writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);return writeCellStyle;}}
再将自定义样式策略注册到Excel中:
java"> //数据表格的自定义列样式CustomVerticalCellStyleStrategy customVerticalCellStyleStrategy= new CustomVerticalCellStyleStrategy();// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(response.getOutputStream(), ExcelDemo.class).registerWriteHandler(customVerticalCellStyleStrategy).sheet("模板").doWrite(getDatas());
上面是简单的示例,具体每一列的样式,可以根据自己的需要定义。
12、自定义单元格
通过实现 CellWriteHandler
接口,可以完全自定义单元格的写入行为和样式。
CellWriteHandler 接口,继承自 WriteHandler,用于在 Excel 写入过程中处理单元格的不同生命周期阶段。每个方法都处理特定的操作,通常用于 Excel 文件的写入时,针对单元格的创建、数据转换、处理和销毁等环节进行自定义处理。
这个接口的作用是提供钩子方法(默认方法)来让用户在写入数据到 Excel 时,能够在每个关键阶段插入自己的逻辑。以下是对每个方法的详细解释:
- 1. beforeCellCreate(CellWriteHandlerContext context)
- 作用:在单元格创建之前调用,用于在写入单元格之前进行一些准备工作。
- 参数:方法内部通过 context 参数提取了多个信息,包括 WriteSheetHolder(写入的工作表)、WriteTableHolder(写入的表格)、Row(当前行)、Head(表头信息)、columnIndex(列索引)、relativeRowIndex(相对行索引)、isHead(是否是表头)等。
- 默认实现:这个方法默认调用了另一个重载版本 beforeCellCreate(WriteSheetHolder, WriteTableHolder, Row, Head, Integer, Integer, Boolean),该重载方法的默认实现为空,表示没有默认操作,用户可以在实现接口时进行自定义。
- 2. beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead)
- 作用:实际执行创建单元格之前的处理逻辑的方法。可以根据需要对 Excel 单元格的创建过程进行自定义操作,如设置格式、填充数据等。
- 参数:接受了各个具体的参数,允许用户在不同的上下文中访问这些信息。
- 3. afterCellCreate(CellWriteHandlerContext context)
- 作用:在单元格创建之后调用。这个方法在单元格已经创建后执行,可以用来处理与单元格创建相关的操作,例如设置样式或处理其他后续步骤。
- 参数:同样是通过 context 提供相关的信息,包括工作表、表格、当前单元格、表头信息、相对行索引等。
- 默认实现:默认调用了另一个重载版本 afterCellCreate(WriteSheetHolder, WriteTableHolder, Cell, Head, Integer, Boolean),该重载方法默认没有实现任何逻辑,允许用户进行自定义。
- 4. afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
- 作用:执行单元格创建后的一些自定义处理逻辑。与 beforeCellCreate 方法类似,但在单元格已创建之后进行操作。
- 参数:该方法接受详细的参数,允许用户访问到当前工作表、单元格、表头等信息。
- 5. afterCellDataConverted(CellWriteHandlerContext context)
- 作用:在单元格的数据被转换(例如格式化、数据类型转换)之后调用。这个方法允许用户在数据转换后进行一些额外的处理,比如修改转换后的数据或进一步调整单元格的内容。
- 参数:context.getCellDataList() 提供了当前单元格的数据,方法会根据需要选择第一个数据进行处理(假设该列表不为空)。其他参数和前面的钩子方法类似。
- 6. afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
- 作用:执行数据转换后的处理逻辑。用户可以在此时进一步调整数据内容、修改样式或执行其他操作。
- 参数:cellData 是转换后的数据,允许用户访问和操作。
- 7. afterCellDispose(CellWriteHandlerContext context)
- 作用:在单元格被销毁之前调用。这个方法是在单元格生命周期的最后阶段,用于清理或执行一些最终的操作。通常会在单元格数据处理和格式化完成后进行一些额外的操作,如记录日志或执行清理工作。
- 参数:context.getCellDataList() 提供了当前单元格的所有数据列表,用户可以在此进行处理。
- 8. afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
- 作用:处理单元格销毁之后的操作。此方法接收了与前面方法相同的详细参数,允许用户在最后一步进行操作。
- 参数:cellDataList 包含了所有需要处理的单元格数据,cell 是当前单元格,head 是表头信息,isHead 是一个布尔值,指示是否为表头行
总结:
这个接口主要用于对 Excel 写入过程中的每个关键步骤提供自定义的处理机制。通过实现CellWriteHandler 接口,用户可以在:
- 单元格创建之前 (beforeCellCreate)
- 单元格创建之后 (afterCellCreate)
- 数据转换之后 (afterCellDataConverted)
- 单元格销毁之前 (afterCellDispose)
等多个阶段进行干预和扩展,实现自定义的单元格处理逻辑。例如,可以用于设置单元格样式、数据格式化、数据验证、日志记录等。
实际上上述的方法都可以进行单元格的样式设置,但是每个方法代表不同的时期,不同的时期能获取到的信息不同,所以可以选择合适的方法来进行自定义格式,但是其实如果不是有复杂的需求,其实不推荐使用该方法进行自定义单元格样式。
简单示例:
java"> EasyExcel.write(response.getOutputStream(), ExcelDemo.class).registerWriteHandler(new CellWriteHandler() {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 当前事件会在 数据设置到poi的cell里面才会回调// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {// 第一个单元格// 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellDataWriteCellData<?> cellData = context.getFirstCellData();// 这里需要去cellData 获取样式// 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat// ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了// 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDwriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了}}}).sheet("模板").doWrite(getDatas());
使用poi的样式设置,用的很少也不推荐使用,只做简单使用:
java">// 方法3: 使用poi的样式完全自己写 不推荐// @since 3.0.0-beta2// 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效// 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CellWriteHandler() {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 当前事件会在 数据设置到poi的cell里面才会回调// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {Cell cell = context.getCell();// 拿到poi的workbookWorkbook workbook = context.getWriteWorkbookHolder().getWorkbook();// 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式// 不同单元格尽量传同一个 cellStyleCellStyle cellStyle = workbook.createCellStyle();cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDcellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);cell.setCellStyle(cellStyle);// 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到// cell里面去 会导致自己设置的不一样context.getFirstCellData().setWriteCellStyle(null);}}}).sheet("模板").doWrite(data());
13、合并单元格
可以配合注解:
@ContentLoopMerge
@OnceAbsoluteMerge
来进行单元格的合并。
java">// 将第6-7行的2-3列合并成一个单元格@OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class ExcelDemo {// 这一列 每隔2行 合并单元格@ContentLoopMerge(eachRow = 2)@ExcelProperty({"姓名"})private String name;@ExcelProperty({"年龄"})private int age;@ExcelProperty({"薪水"})private BigDecimal salary;@ExcelProperty({"地址"})private String address;@ExcelProperty({"日期"})private LocalDateTime dateTime;}