✨此项目目前处于BETA版本✨
此项目基于 Apache POI 框架,用于处理文档内容如Excel工作簿等。
通过该框架,用户可以轻松读取、写入、以及操作文件中的数据,支持对不同格式的文件进行处理。
项目利用 Apache POI 提供的丰富功能,实现了对大型文档的高效处理,并提供了灵活的接口,方便用户根据需求定制化操作。
无论是数据导入、导出,还是对内容进行复杂的编辑和分析,此框架都为用户提供了便捷而强大的解决方案,使得文档的处理变得更加高效、灵活。
Axolotl意为美西螈(ˈæksəˌlɒtəl),总的来说,美西螈以其独特的外观、再生能力和生活习性而备受关注。
发布时间:[2024-09]
- 🎉**(重要功能)**新增读取Map拓展功能,可自由添加MapDocker赋予单元格更多类型,详情查看使用文档。
- 默认校验组接口AxolotlValid改为AxolotlValid.Simple内部接口,将接口进一步隔离。
- 将绑定注解的format分离为全局变量和注解变量,提升灵活性。
请参考文件 📂ChangeLog变更记录说明
支持的文件格式 | 目前支持功能 |
---|---|
Excel(.xlsx,.xls) | 导入/导出 |
PDF(.pdf) | 🔜计划支持 |
✅Java17版本(LTS版本,推荐使用)
<dependency>
<groupId>cn.toutatis</groupId>
<artifactId>axolotl</artifactId>
<version>1.0.17</version>
</dependency>
⛔Java8版本(维护状态)
<dependency>
<groupId>cn.toutatis</groupId>
<artifactId>axolotl</artifactId>
<version>1.0.17-8</version>
</dependency>
📖Excel文件支持类型:
MIME-TYPE | 说明 | 文件后缀 |
---|---|---|
application/vnd.ms-excel | Excel 97-2003文件版本 | [.xls] |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Excel 2007及以上版本 | [.xlsx] |
// 使用静态方法获取Excel读取器(推荐)
AxolotlExcelReader<Object> excelReader1 = Axolotls.getExcelReader(file);
// 流读取支持
AxolotlExcelReader<Object> excelInsReader = Axolotls.getExcelReader(inputStream);
// 或者使用构造方法创建
AxolotlExcelReader<Object> excelReader2 =new AxolotlExcelReader<>(excelFile);
// 1.打开一个Excel文件
File file = new File("打开一个Excel文件.xlsx");
// 2.新建一个Excel读取器
AxolotlExcelReader<Object> excelReader = Axolotls.getExcelReader(file);
// 3.读取数据
List<Object> data = excelReader.readSheetData();
System.out.println(data);
[Ver.1.0.17新增功能] 读取Map时读取器增加拓展方法:
// 该方法将单元格值和拓展类型拍平为map中的key来区分单元格中的不同属性,例如CELL_0为第列,CELL_0@PLAIN_TEXT为CELL_0的文本值
List<Map<String, Object>> maps = excelReader.readSheetDataAsFlatMap(null);
// 该方法将行中单元格转为AxolotlCellMapInfo对象,对象中包含单元格原值,转换值,单元格类型等
List<Map<String, AxolotlCellMapInfo>> maps = excelReader.readSheetDataAsMapObject(null);
详情查看Map拓展坞【📌点击跳转至对应章节】。
本框架支持写入XLSX文件。
// 最重要的是需要创建写入配置
WriterConfig commonWriteConfig = new WriterConfig();
// 创建模板Excel写入器
File file = FileToolkit.getResourceFileAsFile("写入模板.xlsx");
AxolotlExcelWriter axolotlAutoExcelWriter = Axolotls.getTemplateExcelWriter(file, commonWriteConfig);
// 创建普通Excel写入器
AxolotlExcelWriter axolotlAutoExcelWriter = Axolotls.getExcelWriter(commonWriteConfig);
// 获取数据
Map<String, Object> map = Map.of("name", "Toutatis");
List<TestEntity> datas = new ArrayList<>();
// 调用模板写入方法
axolotlAutoExcelWriter.writeToTemplate(0, map, datas);
// 创建配置
AutoWriteConfig config = new AutoWriteConfig();
config.setTitle("设置标题");
// 设置输出位置
config.setOutputStream(new FileOutputStream("test.xlsx"));
List<Header> headers = new ArrayList<>();
// 添加表头和数据
headers.add(new Header("添加表头"));
ArrayList<POJO> data = new ArrayList<>();
// 创建写入器
AxolotlAutoExcelWriter aaew = Axolotls.getAutoExcelWriter(config);
// 写入数据
autoExcelWriter.write(headers,data);
// TODO 等待支持
🔆在构建AxolotlExcelReader后,使用readSheetData(args)读取数据,readSheetData有多种形参方法,详情请查看readSheetData方法源代码,基本上均为readSheetData(ReaderConfig readerConfig)的变种使用【📌点击跳转至对应章节】。
🔆框架支持读取Excel为List或者为单个Object实例。
// 将所有实体属性指定为@SpecifyPositionBind可以读取为一个Object
POJO data = reader.readSheetDataAsObject(ReaderConfig readerConfig)
// 读取excel为List数据
List<POJO> data = reader.readSheetData(ReaderConfig readerConfig)
注解(annotations) | 用途 | 参数说明 |
---|---|---|
@IndexWorkSheet | [Class] 指定具体索引的工作表 |
[readRowOffset]读取起始偏移行 [sheetIndex]工作表索引[默认值:0] [sheetColumnEffectiveRange]工作表列有效范围[默认范围:{0,-1}] |
@NamingWorkSheet | [Class] 指定具体名称的工作表(区分大小写) |
[readRowOffset]读取起始偏移行 [sheetName]工作表名称 |
@ColumnBind* | [Property] 实体绑定列位置 |
[columnIndex]列索引 [format]日期格式化(数据格式化暂不支持) [adapter]数据适配器 [headerName]表头名称 |
@SpecifyPositionBind | [Property] 实体绑定具体单元格位置 |
[value]单元格位置[举例:A1,B2,C3] [format]日期格式化(数据格式化暂不支持) [adapter]数据适配器 |
@KeepIntact | [Property] 赋值字段时排除读取策略 |
[excludePolicies]排除的读取策略 |
🧭POJO示例:
@Data
// 指定索引工作表
@IndexWorkSheet(sheetIndex=0)
public class TestEntity {
// JSR-303支持
@NotBlank
// 读取第0列
@ColumnBind(columnIndex = 0)
private String name;
@Min(value = 1,message = "AAA")
// 排除特性
@KeepIntact(excludePolicies = ReadPolicy.CAST_NUMBER_TO_DATE)
private int age;
// 可以指定列表头对应的列位置
@ColumnBind(headerName = "备注")
private String remark;
// 指定具体的单元格位置
@SpecifyPositionBind("A1")
private String title;
}
读取数据量大时支持迭代器读取。
AxolotlExcelReader<POJO> excelReader = Axolotls.getExcelReader(file, POJO.class);
// 配置表级配置(一般不推荐配置,属于内部维护变量)
excelReader.set_sheetLevelReaderConfig(readerConfig);
while (excelReader.hasNext()){
List<POJO> next = excelReader.next();
}
一般读取来说,若无特殊读取需求,可以直接构造读取器。
AxolotlExcelReader<TestEntity> reader = Axolotls.getExcelReader(file.getInputStream(), TestEntity.class);
// 根据方法中不同形参读取
List<TestEntity> list = reader.readSheetData(7);
🔆若是需要更灵活的配置需要构造ReaderConfig配置类。
readSheetData的根读取方法为 readSheetData(readerConfig)
ReaderConfig<CastClass> readerConfig = new ReaderConfig<>(castClass);
List<TestEntity> list = reader.readSheetData(readerConfig);
📖ReaderConfig可配置项:
配置项 | 说明 | 必填 | 默认值 |
---|---|---|---|
构造器(Class castClass) | 设置读取类 | 是 | 无 |
构造器(boolean withDefaultConfig) | 是否使用默认配置 | 是 | True |
Class castClass | 设置读取类 | 是 | 无 |
sheetIndex | 工作表索引 | 否 | 0 |
sheetName | 工作表名称 | 否 | 无 |
initialRowPositionOffset | 初始行偏移量 | 否 | 0 |
startIndex | 读取起始行 | 否 | 0 |
endIndex | 读取结束行 | 否 | -1(所有) |
索引映射 (一般不用指定) |
否 | 无 | |
单元格映射 (一般不用指定) |
否 | 无 | |
rowReadPolicyMap | 策略集合 | 否 | 参考默认策略 |
searchHeaderMaxRows | 搜索表头最大行 | 否 | 10 |
sheetColumnEffectiveRange | 工作表有效列范围 arr[1] = -1表示该Row所有列 |
否 | {0,-1} |
在读取Excel文件数据时,读取到数据时会有默认的读取策略。
在使用ReaderConfig时,可以使用以下方法指定读取策略。
// 使用 new ReaderConfig<>(false);可取消所有读取策略,基本上以异常抛出形式作为错误,默认为true,包含以下所有默认读取策略
ReaderConfig<T> readerConfig = new ReaderConfig<>(castClass);
// 使用setBooleanReadPolicy方法可指定读取策略
// 在此实例中,忽略空表异常(IGNORE_EMPTY_SHEET_ERROR)指定为true时,将返回一个空的读取列表,在指定为false时将抛出空表异常
readerConfig.setBooleanReadPolicy(ReadPolicy.IGNORE_EMPTY_SHEET_ERROR, false);
📖读取策略说明:
读取策略枚举 | 中文说明 | 策略类型 | 是否为默认策略 | 默认值 |
---|---|---|---|---|
IGNORE_EMPTY_SHEET_ERROR | 忽略空表异常 | Boolean | true | true |
IGNORE_EMPTY_SHEET_HEADER_ERROR | 忽略空表头的错误 | Boolean | true | true |
SPREAD_MERGING_REGION | 将合并的单元格展开到合并单元格的各个单元格 | Boolean | true | true |
ALLOW_READ_HIDDEN_SHEET | 允许读取隐藏表 | Boolean | true | false |
INCLUDE_EMPTY_ROW | 空行也视为有效数据 | Boolean | true | false |
SORTED_READ_SHEET_DATA | 在使用Map接收时,使用LinkedHashMap | Boolean | true | true |
CAST_NUMBER_TO_DATE | 判断数字为日期类型将转换为日期格式 | Boolean | true | true |
DATA_BIND_PRECISE_LOCALIZATION | 指定此特性,在按行读取时,若没有指定列名,将不会绑定对象属性 否则将按照实体字段顺序自动按照索引绑定数据 |
Boolean | true | true |
TRIM_CELL_VALUE | 修整单元格去掉单元格所有的空格和换行符 | Boolean | true | true |
USE_MAP_DEBUG | 使用Map接收数据时,打印调试信息 | Boolean | true | true |
FIELD_EXIST_OVERRIDE | 如果字段存在值覆盖掉原值 | Boolean | true | true |
MAP_ALLOW_PUT_NULL_VALUE | 允许map中put Null值 false将忽略空值,不put对应Key值 |
Boolean | true | true |
MAP_CONVERT_INFO_OBJECT | 读取数据时,将数据转换成Map信息对象 | Boolean | true | true |
VALIDATE_READ_ROW_DATA | 读取数据后校验数据 | Boolean | true | true |
本框架支持Bean Validation,使用Hibernate-Validator进行实体属性校验。
详情请参考 📂【Hibernate-Validator 8.0.1.Final 使用手册】 (Java8版本使用为6.2.5.Final)
校验时请指定groups分组参数AxolotlValid.class以便于实体校验器校验实体导入字段是否正常。
@NotBlank(message = "name is blank", groups = {AxolotlValid.class})
private String name;
手动校验可以使用工具类校验:
EntityValidator.INSTANCE().validate(testEntity, Insert.class);
📖一般POJO类使用注解如下:
注解 | 说明 |
---|---|
@Nul | 被注释的元素必须为 Null |
@NotNull | 被注释的元素必须不为 Null |
@AssertTrue | 被注释的元素必须为 True |
@AssertFalse | 被注释的元素必须为 False |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内,元素必须为集合,代表集合个数 |
@Pattern(regexp = ) | 正则表达式校验 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
被注释的元素必须是电子邮箱地址 | |
@Length(min=, max=) | 被注释的字符串的大小必须在指定的范围内,必须为数组或者字符串,若微数组则表示为数组长度,字符串则表示为字符串长度 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range(min=, max=) | 被注释的元素必须在合适的范围内 |
@NotBlank | 被注释的字符串的必须非空 |
@URI | 字符串是否是一个有效的URL |
在读取文件时难免会有读取错误的情况,在发生读取异常时会抛出AxolotlExcelReadException来提示异常信息。
🔆推荐使用为在Web框架使用时向上层抛出异常,在全局异常处理器中统一返回错误信息
📖AxolotlExcelReadException异常中包含以下内容:
可获取内容 | 说明 |
---|---|
message | 错误信息 |
currentReadRowIndex | 当前读取行数 |
currentReadColumnIndex | 当前读取列数 |
humanReadablePosition | 良好可读性的错误位置(示例:A5单元格) |
fieldName | 错误的实体属性 |
exceptionType | 读取错误类型 |
📖AxolotlExcelReadException.ExceptionType错误类型说明:
错误枚举 | 说明 |
---|---|
READ_EXCEL_ERROR | 读取Excel文件时出现了异常 |
READ_EXCEL_ROW_ERROR | 读取Excel数据时,出现了异常 |
CONVERT_FIELD_ERROR | 转换数据时出现异常 |
VALIDATION_ERROR | 校验数据时出现异常 |
在读取大的Excel文件(文件大小>=15M)时,将文件转换为数据加载进内存时会占用大量的时间和内存,在单个Sheet中数据40w行数据左右时将分配10G内存,时间在1min左右。
在读取此类大文件时可以使用 AxolotlStreamExcelReader 以流的方式读取数据,减少加载时间和内存占用,该读取器相较于AxolotlExcelReader 失去了很多特性,例如获取指定位置数据,分页等。
在使用流读取器时只能使用迭代器获取表中数据,并且只能支持XLSX格式。
// 获取流读取器
AxolotlStreamExcelReader<Object> streamExcelReader = Axolotls.getStreamExcelReader(file);
int recordRowNumber = streamExcelReader.getRecordRowNumber();
System.err.println(recordRowNumber);
// 构建配置(在流读取下表位置,开始位置,结束位置等设置均无用)
ReaderConfig<TestEntity> readerConfig = new ReaderConfig<>(TestEntity.class);
// 获取迭代器
AxolotlExcelStream<TestEntity> dataIterator = streamExcelReader.dataIterator(readerConfig);
int idx = 0;
// 读取数据
while (dataIterator.hasNext()){
TestEntity entity = dataIterator.next();
System.out.println(idx+"="+entity);
idx++;
}
在读取此类大文件时可以使用 AxolotlStreamExcelReader 以流的方式读取数据,减少加载时间和内存占用,该读取器相较于AxolotlExcelReader 失去了很多特性,例如获取指定位置数据,分页等。
实体类型使用**数据适配器(DataCastAdapter)**将Excel单元格转换为Java实体对应属性的的处理器,在获取到单元格数据时适配为转换类实例对象中对应字段类型并赋值。
📖适配器的配置:
- 通过注解指定adapter:
@ColumnBind(headerName = "工程名称",adapter = DefaultStringAdapter.class)
private String a1;
@SpecifyPositionBind(value = "A1",adapter = DefaultStringAdapter.class)
private String a2;
可在注解 @ColumnBind与@SpecifyPositionBind 中直接指定该属性使用的适配器,需注意适配器是否支持该属性类型与单元格类型的转换。
- 全局指定
全局指定时可通过配置DefaultAdapters类实现,注解指定优先级高于全局指定。
(有关默认适配器信息请看下一小节)
📖DefaultAdapters[默认适配器]:
默认数据适配器,框架为基本数据类型以及常用Java类提供预设数据适配器。
- DefaultBooleanAdapter(布尔类处理器)-> STRING,BOOLEAN,NUMERIC -> Boolean
- DefaultDateTimeAdapter(时间类适配器)-> STRING,NUMERIC -> LocalDateTime等
- DefaultNumericAdapter(数字类适配器)-> STRING,NUMERIC -> Interger等
- DefaultStringAdapter(字符串类处理器)-> ALL -> String
通过DefaultAdapters该类方法可完成全局适配器的配置,使用registerDefaultAdapter方法注册全局适配器,使用removeDefaultAdapter方法移除全局适配器(基础类型适配器不能被移除)。
添加其他适配器为默认适配器:
// 调用registerDefaultAdapter方法注册一个适配器为全局适配器
// clazz为需要转换的Java类型
DefaultAdapters.registerDefaultAdapter(clazz,adapter);
移除默认适配器:
// clazz为需要转换的Java类型
DefaultAdapters.removeDefaultAdapter(clazz);
📖自定义适配器:
所有适配器都为DataCastAdapter 接口实现类。
DataCastAdapter接口:
public interface DataCastAdapter<FT> {
/**
* 类型转换
* @param cellGetInfo 单元格信息
* @param context 类型转换所需的上下文
* @return 转换后的值
*/
FT cast(CellGetInfo cellGetInfo, CastContext<FT> context);
/**
* 是否支持该类型进行转换
* @param cellType 单元格类型
* @param clazz 需要转换的类型
* @return 是否支持该类型进行转换
*/
boolean support(CellType cellType, Class<FT> clazz);
}
接口中有两个方法cast(转换)与support(是否支持转换)。
cast方法:
将Excel的单元格类型转换为Java类型的方法,转换逻辑在此方法中实现。
方法形参:
参数 | 说明 |
---|---|
CellGetInfo cellGetInfo | 单元格信息:包含数据,单元格类型等 |
CastContext context | 转换上下文:包含转换字段类型等 |
cellGetInfo 为单元格相关信息,**context **为类型转换所需的上下文信息
support方法:
判断该适配器是否支持从单元格类型转换为Java字段类型。
cellType 为单元格类型,clazz 为Java类型,返回true说明该适配器支持转换,反之不支持转换并抛出异常。
适配器抽象基类 AbstractDataCastAdapter(推荐使用)
框架中的默认实现皆由此类实现:
public abstract class AbstractDataCastAdapter<FT> implements DataCastAdapter<FT> {
/**
* 读取配置
*/
private ReaderConfig<?> readerConfig;
/**
* 实体映射信息
*/
private EntityCellMappingInfo<?> entityCellMappingInfo;
}
AbstractDataCastAdapter 提供了读取配置readerConfig与实体映射信息entityCellMappingInfo两种配置信息供开发者在编写适配器时使用。
参数 | 说明 |
---|---|
ReaderConfig<?> readerConfig | 为读取表格时的相关配置信息,包括已启用的读取特性、读取的sheet表相关信息、读取表头的范围信息(当以表头名称绑定列时可用)、实体类属性与表格的映射信息(所有属性)以及读取的行次、列次范围信息等等。 |
EntityCellMappingInfo<?> entityCellMappingInfo | 主要为当前属性的相关映射信息,如属性类型、属性名称、对应单元格的行次列次、单元格映射类型以及该属性排除的读取特性等等。 |
映射类型(Map)由于开发者无法对获取的值进行类型转换,可使用**MapDocker(数据坞)**进行字段拓展,在单元格原值的基础上拓展出多个转换值。
📖功能说明:
举例单元格值为2024-10-01,在POI中获取的值为(Double)45566,为什么是45566,因为Excel中使用的日期系统基准日期被称为"1900 Date System",Excel将1900年1月1日定义为日期序列值1,每天依次累加。在这种情况下,使用Map读取时框架无法知道转换类型,获取到值为浮点数类型,但我们实际需要的可能是字符串类型,也可能是日期类型,在这种情况下我们需要转换为对应格式,MapDocker的出现解决了该问题。
在策略MAP_CONVERT_INFO_OBJECT的模式下,单元格Map信息会转换为AxolotlCellMapInfo对象,并将该值拓展dockerValues属性中并以DATE_LOCAL_TIME作为Key的一个LocalDateTime格式的值。
📖MapDocker使用说明:
在MAP_CONVERT_INFO_OBJECT策略下,Map的Value会转换为AxolotlCellMapInfo类,该类具有如下属性:
属性值 | 类型 | 说明 |
---|---|---|
cellIndex | int | cellIndex存储了当前信息对象对应的单元格索引这个索引用于标识信息对象在数据结构中的位置 |
originalValue | Object | originalValue存储了原始的单元格值这个值未经过任何处理,保持原始数据的形态 |
cellType | CellType | cellType获取单元格的类型,这有助于理解单元格数据的含义和如何处理这些数据 |
dockerValues | Map<String,Object> | dockerValues是一个键值对集合,用于存储多个MapDocker转换后的值 |
在MAP_CONVERT_INFO_OBJECT策略为False时,Map依旧保持为Map键值对的形式,但每个单元格会拓展为多个Key并以@作为分隔符添加,如以下示例:
Key(Index为单元格索引) | Value |
---|---|
Cell_[Index] | 单元格原值 |
Cell_[Index]@TYPE(启用USE_MAP_DEBUG策略时添加) | 单元格类型 |
Cell_[Index]@PLAIN_TEXT | 单元格原值文本值 |
📖如何编写MapDocker:
-
创建一个MapDocker,编写如何从原始值转为需求值
package cn.xvoid.axolotl.excel.reader.support.docker.impl; import cn.xvoid.axolotl.excel.reader.ReaderConfig; import cn.xvoid.axolotl.excel.reader.constant.ExcelReadPolicy; import cn.xvoid.axolotl.excel.reader.support.CellGetInfo; import cn.xvoid.axolotl.excel.reader.support.docker.AbstractMapDocker; import cn.xvoid.common.standard.StringPool; import cn.xvoid.toolkit.constant.Regex; import cn.xvoid.toolkit.number.NumberToolkit; import org.apache.poi.ss.usermodel.CellType; import java.text.DecimalFormat; /** * 纯文本映射Docker实现类 * 用于处理Excel单元格中的纯文本数据 * @author Toutatis_Gc */ public class PlainTextMapDocker extends AbstractMapDocker<String> { /** * 定义该Docker的后缀名 */ public static final String SUFFIX_NAME = "PLAIN_TEXT"; /** * 用于格式化数字的DecimalFormat实例 */ private final DecimalFormat decimalTextFormat = new DecimalFormat(StringPool.HASH); /** * 获取该Docker的后缀名 * * @return Docker的后缀名 */ @Override public String getSuffix() { return SUFFIX_NAME; } /** * 转换Excel单元格值为纯文本 * * @param index 单元格索引 * @param cellGetInfo 单元格获取信息 * @param readerConfig 读取配置 * @return 转换后的纯文本字符串 */ @Override public String convert(int index, CellGetInfo cellGetInfo, ReaderConfig<?> readerConfig) { Object cellValue = cellGetInfo.getCellValue(); if (cellValue == null) { return null; } String formatValue; if (cellGetInfo.getCellType() == CellType.NUMERIC) { // 使用预定义的格式化规则处理数值型单元格 if (NumberToolkit.isInteger((Double) cellValue)){ formatValue = decimalTextFormat.format(cellValue); }else{ formatValue = cellValue.toString(); } } else { // 直接转换非数值型单元格为字符串 formatValue = cellValue.toString(); } if (readerConfig.getReadPolicyAsBoolean(ExcelReadPolicy.TRIM_CELL_VALUE)) { // 根据读取策略,可能需要去除单元格值的前后空格 formatValue = Regex.convertSingleLine(formatValue).replace(" ", ""); } return formatValue; } }
-
ReaderConfig设置该MapDocker使其生效
// 该方法可以自定义后缀 readerConfig.setMapDocker("EXAMPLE", exampleMapDocker); // 使用exampleMapDocker.getSuffix();默认后缀 readerConfig.setMapDocker(exampleMapDocker);
📖默认支持的MapDocker:
拓展类型 | 空值是否添加 | 值 |
---|---|---|
PLAIN_TEXT | True | 单元格值字符串 |
DATE_FMT | False | 单元格格式化值(使用ReaderConfig.setGlobalDateFormat()设置) |
LOCAL_DATE_TIME | False | 单元格值LocalDateTime类型 |
在实体中指定字段类型为AxolotlReadInfo时候,读取表中数据将自动赋值该字段,该类中包含以下内容。
📖AxolotlReadInfo字段:
字段名称 | 说明 |
---|---|
sheetIndex | 工作表索引 |
sheetName | 工作表名称 |
rowNumber | 工作表中该实体行号 |
每个实体中仅支持一个该字段属性,其余相同类型字段将被覆盖。
本框架仅支持XLSX文件写入,性能更优异兼容更好。
所有写入方法注意事项:大批量写入时避免合并单元格写入,将降低大量性能!!!
所有写入方法注意事项:大批量写入时避免合并单元格写入,将降低大量性能!!!
所有写入方法注意事项:大批量写入时避免合并单元格写入,将降低大量性能!!!
确保你已经准备好一个模板文件,该文件包含了你希望在其中填充数据的格式,例如,包含了表头、样式等。
模板写入将根据模板中占位符替换数据。
// 添加一个模板文件
File file = FileToolkit.getResourceFileAsFile("workbook/write/.xlsx");
//创建写入配置
WriterConfig commonWriteConfig = new WriterConfig();
FileOutputStream fileOutputStream = new FileOutputStream("D:\\" + IdUtil.randomUUID() + ".xlsx");
commonWriteConfig.setOutputStream(fileOutputStream);
// 创建写入器
try (AxolotlExcelWriter axolotlAutoExcelWriter = new AxolotlExcelWriter(file, commonWriteConfig)) {
// 获取数据
Map<String, Object> map = Map.of("name", "Toutatis","nation","汉");
axolotlAutoExcelWriter.writeToTemplate(0, map, null);
ArrayList<JSONObject> datas = new ArrayList<>();
for (int i = 0; i < 3; i++) {
JSONObject sch = new JSONObject();
sch.put("schoolName","北京-"+RandomStringUtils.randomAlphabetic(16));
sch.put("schoolYears", RandomUtil.randomBigDecimal(BigDecimal.ZERO, BigDecimal.TEN).setScale(0, RoundingMode.HALF_UP));
sch.put("graduate", true);
datas.add(sch);
}
// 调用写入模板方法
axolotlAutoExcelWriter.writeToTemplate(0, Map.of("age",50), datas);
}
模板写入必须要在模板单元格中指定占位符。
占位符 | 说明 | 示例 |
---|---|---|
${} | 映射占位符 占位符只能使用一次 |
${name} |
#{} | 列表数据占位符 写入多条数据时指定该占位符 |
#{schoolName} |
&{} | 合计占位符**(仅支持合计列表数据)** 使用方法和映射占位符一致 |
&{money} |
占位符默认值:
在使用占位符时,如果数据为空(null,不是空字符串)时,可使用占位符指定默认值。
占位符格式 | 说明 |
---|---|
${admin:系统管理员} | map中,admin为null时将被填充为系统管理员字符串 |
#{money:0} | 列表数据中金额为null时将被填充为0 |
写入模板数据时,框架拥有一些内置常量。
常量名称 | 说明 |
---|---|
AXOLOTL_CREATE_TIME | 当前写入时间[格式:yyyy-MM-dd hh:MM:ss] |
AXOLOTL_CREATE_DATE | 当前写入日期[格式:yyyy-MM-dd] |
🧭示例表格:
自动写入为按照预定的主题/样式写入数据。
// 初始化写入文件
FileOutputStream fos = new FileOutputStream("D:\\test.xlsx");
AutoWriteConfig commonWriteConfig = new AutoWriteConfig();
// 设置写入相关信息
commonWriteConfig.setTitle("股票测试表");
commonWriteConfig.setBlankValue("-");
commonWriteConfig.setOutputStream(fos);
// 设置表头
List<Header> headers = new ArrayList<>();
headers.add(new Header("代码","code"));
headers.add(new Header("简称","intro"));
headers.add(new Header("最新日期","localDateTimeStr"));
headers.add(new Header("最新收盘(元)",StockEntity::getClosingPrice));
headers.add(new Header("涨跌幅(%)",StockEntity::getPriceLimit));
headers.add(new Header("总市值(亿元)",StockEntity::getTotalValue));
headers.add(new Header("流通市值(元)",StockEntity::getCirculationMarketValue));
List<Header> subHeader1 = List.of(new Header("TTM"), new Header("15E"),new Header("16E"));
headers.add(new Header("每股收益", subHeader1));
headers.add(new Header("市盈率PE", subHeader1));
headers.add(new Header("市净率PB(LF)"));
headers.add(new Header("市销率PS(TTM)","pts"));
// 创建写入数据
ArrayList<StockEntity> data = new ArrayList<>();
for (int i = 0; i < 50; i++) {
StockEntity stockEntity = new StockEntity();
stockEntity.setCode(RandomStringUtils.randomNumeric(8));
// ...忽略字段写入
data.add(stockEntity);
}
//创建写入器
AxolotlAutoExcelWriter autoExcelWriter = Axolotls.getAutoExcelWriter(commonWriteConfig);
autoExcelWriter.write(headers,data);
autoExcelWriter.close();
-
表头Header:
Header类为工作表的表头,使用autoExcelWriter.write(headers,data)方法写入数据时将为工作表创建表头,支持嵌套表头, 表现为多行合并表头。在主题说明章节处可以查看样式。
📖表头属性说明:
属性 说明 备注 name 表头标题 表头显示名称。 fieldName 数据字段映射 实体类的对应字段,指定该字段后写入将只按照映射写入,不按照数据的字段定义顺序写入列,必须指定所有字段映射,否则将置入空值,最底层节点生效。 childs 子表头 当前表头的下级表头。 customCellStyle 表头自定义样式 POI样式,适合高级使用。
优先级:高axolotlCellStyle 表头自定义样式 对于不熟悉POI的使用者使用此属性创建单元格样式,指定了customCellStyle后此属性将不生效。
优先级:低columnWidth 当前表头的列宽 仅最底层节点生效。 participateInCalculate 是否参与计算列 表头所对应列是否参与合计。(合计特性)
仅最底层节点生效。
写入配置使用AutoWriteConfig类进行写入。
📖AutoWriteConfig配置属性说明:
属性 | 说明 | 备注 |
---|---|---|
title | 工作表标题 | 配置此属性将创建工作表标题行样式。 |
sheetName | 工作簿表名称 | 未配置此属性时将使用title属性值。 |
metaClass | 数据类 | 配置此类将获取该类的表头标题等注解。 |
fontName | 自定义字体名称 | 配置字体名称将覆盖主题默认字体。 |
styleRender | 样式渲染器 | 使用样式渲染器渲染内容。 |
dataInverter | 数据转换器 | 将字段数据转为工作表数据。(默认渲染为字符串) 如有需要将在未来版本支持其他数据格式。 |
blankValue | 空值填充字符 | 写入数据时,实体类中Null值将被填充为空字符串。 常用的字符串有"","-","未填写","无"等。 |
specialRowHeightMapping | 特殊行高映射 | 添加到此map的行次将配置行高。 |
calculateColumnIndexes | 需要计算的列索引 | 在使用自动在结尾插入合计行特性时生效,默认计算所有列。 可能会将编号等字段也作为合计列,此时可以使用此字段自定义合计列。 |
🧭如何配置主题?
使用AutoWriteConfig配置类调用setThemeStyleRender方法配置主题样式。
AutoWriteConfig autoWriteConfig = new AutoWriteConfig();
autoWriteConfig.setThemeStyleRender(theme);
🧭内置主题样式:
🧭如何自定义主题或定制样式:
在写入数据时,支持写入策略来对数据进行处理。
策略名称 | 使用范围 | 说明 |
---|---|---|
AUTO_CATCH_COLUMN_LENGTH | 自动写入 | 自动计算列长度 |
AUTO_INSERT_SERIAL_NUMBER | 自动写入 | 自动在第一列插入编号 |
AUTO_FILL_DEFAULT_CELL_WHITE | 自动写入 | 默认填充单元格为白色 |
AUTO_INSERT_TOTAL_IN_ENDING | 自动写入 | 自动在结尾插入合计行 |
AUTO_HIDDEN_BLANK_COLUMNS | 自动写入 | 默认隐藏工作表空白列 将影响导出性能和增大存储空间 |
TEMPLATE_SHIFT_WRITE_ROW | 模板写入 | 数据写入自动将数据写入下移到下一行 |
TEMPLATE_PLACEHOLDER_FILL_DEFAULT | 模板写入 | 为没有指定的占位符填充默认值 |
TEMPLATE_NULL_VALUE_WITH_TEMPLATE_FILL | 模板写入 | 空值是否使用模板填充 |
TEMPLATE_NON_TEMPLATE_CELL_FILL | 模板写入 | 非模板单元格是否模板填充 |
SIMPLE_EXCEPTION_RETURN_RESULT | 通用 | 异常将返回结果,不抛出异常 |
SIMPLE_USE_GETTER_METHOD | 通用 | 是否使用getter方法,取消则直接使用反射字段值 |
SIMPLE_USE_DICT_CODE_TRANSFER | 通用 | 是否使用字典转换 |
SIMPLE_CALCULATE_INTEGER_IGNORE_DECIMAL | 通用 | 计算数据为整数数据时,忽略默认小数位,展示为整数 |
// 等待Feature支持
本框架将logging.level等级设置为DEBUG可见调试信息。
🧭示例 log4j.properties 配置如下:
# Set root logger level to DEBUG and its only appender to console.
log4j.rootLogger=DEBUG, console
# console is set to be a ConsoleAppender.
log4j.appender.console=org.apache.log4j.ConsoleAppender
# console uses PatternLayout.
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
日志框架冲突,可将依赖中的日志框架移除。
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
📖官网引用:
This error is reported when the org.slf4j.impl.StaticLoggerBinder class could not be loaded into memory. This happens when no appropriate SLF4J binding could be found on the class path. Placing one (and only one) of slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem.
翻译:无法将org.slf4j.impl.StaticLoggerBinder类装入内存。当在类路径上找不到适当的SLF4J绑定时,就会发生这种情况。将slf4j-nop.jar、slf4j-simple.jar、slf4j-log4j12.jar、slf4j-jdk14.jar或logback-classic.jar中的一个(且只有一个)放在类路径上应该可以解决这个问题。
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>cn.toutatis</groupId>
<artifactId>axolotl</artifactId>
<version>1.0.15</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
读取合并单元格不同列时会读取到同样的内容,因为本框架默认采用的策略为散播策略(SPREAD_MERGING_REGION 可配置,默认开启),会将合并单元格的值散播到合并单元格的各个单元格上(原为合并单元格中第0行,第0列的值)。
该功能的是为了读取数据时直接按照表头名称读取对应列所设计,解决不同模板之间表头有差异造成读取列错位所设计的功能。 指定注解中此参数,会去读取工作表中查找完全匹配的单元格字符串(例如:备注,地址)所对应的列位置转换为所对应的列索引作为读取列(如果有多个同名表头可指定sameHeaderIdx参数区分不同同名列),相当于转化为注解中的columnIndex参数。
📖具体规则如下:
列与实体类属性的绑定:
@ColumnBind注解会根据属性中的headerName的值到工作表中进行单元格数据检索并绑定对应的列位置。 查找规则为按行从上至下依次检索[配置searchHeaderMaxRows进行修改]该行每一列上的单元格,如果这些单元格中的值与配置的headerName的值完全匹配的情况下,那么该单元格的列位置会被判定为表头。
如果表头中只匹配到一个单元格的列,那么该单元格所处的列与该实体类属性绑定,如果备选区存在多个同名表头,可通过@ColumnBind注解的sameHeaderIdx参数[默认为0]指定同名表头索引,被指定的单元格所处的列会与该实体类属性绑定,若不进行指定,则按实体类该属性所处位置(在实体类中headerName参数相同的属性为一组,按属性声明的先后顺序升序排列,属性所处位置就是属性在该组中的位置,这个位置与单元格在备选区的位置对应)选择单元格进行列绑定。
🧭使用建议:
- 在使用headerName参数进行列绑定时,应首先配置ReaderConfig中的searchHeaderMaxRows属性确认表头范围,减少检索范围以提高效率并提高精准度。
- 在使用headerName指定列位置时,应检查工作表范围内是否存在多个相同表头,建议在工作表为动态时应对工作表表头位置进行判断,在需要时可通过配置@ColumnBind注解的sameHeaderIdx参数手动指定备选表头索引进行列绑定。
出现该问题是由于XVOID包功能由其他语言支持,遇到此问题请升级IDEA到最新版。