• EasyExcel的使用


    her~~llo,我是你们的好朋友Lyle,是名梦想成为计算机大佬的男人!

    博客是为了记录自我的学习历程,加强记忆方便复习,如有不足之处还望多多包涵!非常欢迎大家的批评指正。

    最近开发项目需要用到比较复杂的高级导入,于是就学习了EasyExcel的使用,现在总结一下。有什么新内容会持续更新。

    目录

    一、EasyExcelFactory工厂类

    二、自定义EasyExcel工具类

    三、监听器

     四、实例演示


    首先说一下版本吧,我使用的是2.2.7的版本。

    1. <dependency>
    2. <groupId>com.alibaba</groupId>
    3. <artifactId>easyexcel</artifactId>
    4. <version>2.2.7</version>
    5. </dependency>

    一、EasyExcelFactory工厂类

    首先我建议大家可以看一下EasyExcel为我们提供的工厂类EasyExcelFactory代码,其中经常用到的有这些:

    读取文件导入的话就是

    read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();

    写文件导出的话就是

    write(filePath).head(head).sheet(sheetNo, sheetName).doWrite(data);

    看懂源代码的话,对我们自己编写适合自己项目的方法很用用处。

    二、自定义EasyExcel工具类

    实际开发中,我们在导入excel文件时有很多种情况,我在下方各种情况都列举出来了,其中展示了同步读取的情况的对应代码,因为太多了,不方便展示。有需要学习的小伙伴可以私信我。

    1. /**
    2. * EasyExcel工具类
    3. */
    4. public class EasyExcelUtils {
    5. /**
    6. * 同步无模型读取(默认读取sheet0,从第2行开始读)
    7. *
    8. * @param filePath 文件路径
    9. * @return List>
    10. */
    11. public static List> syncRead(String filePath) {
    12. return EasyExcelFactory.read(filePath).sheet().doReadSync();
    13. }
    14. /**
    15. * 同步无模型读取(默认表头占一行,从第2行开始读)
    16. *
    17. * @param filePath 文件路径
    18. * @param sheetNo sheet页号,从0开始
    19. * @return List>
    20. */
    21. public static List> syncRead(String filePath, Integer sheetNo) {
    22. return EasyExcelFactory.read(filePath).sheet(sheetNo).doReadSync();
    23. }
    24. /**
    25. * 同步无模型读取(指定sheet和表头占的行数)
    26. *
    27. * @param inputStream 输入流
    28. * @param sheetNo sheet页号,从0开始
    29. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    30. * @return List>
    31. */
    32. public static List> syncRead(InputStream inputStream, Integer sheetNo, Integer headRowNum) {
    33. return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    34. }
    35. /**
    36. * 同步无模型读取(指定sheet和表头占的行数)
    37. *
    38. * @param file 文件
    39. * @param sheetNo sheet页号,从0开始
    40. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    41. * @return List>
    42. */
    43. public static List> syncRead(File file, Integer sheetNo, Integer headRowNum) {
    44. return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    45. }
    46. /**
    47. * 同步无模型读取(指定sheet和表头占的行数)
    48. *
    49. * @param filePath 文件路径
    50. * @param sheetNo sheet页号,从0开始
    51. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    52. * @return List>
    53. */
    54. public static List> syncRead(String filePath, Integer sheetNo, Integer headRowNum) {
    55. return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    56. }
    57. /**
    58. * 同步按模型读取(默认读取sheet0,从第2行开始读)
    59. *
    60. * @param filePath 文件路径
    61. * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
    62. * @return List
    63. */
    64. public static List syncReadModel(String filePath, Class clazz) {
    65. return EasyExcelFactory.read(filePath).sheet().head(clazz).doReadSync();
    66. }
    67. /**
    68. * 同步按模型读取(默认表头占一行,从第2行开始读)
    69. *
    70. * @param filePath 文件路径
    71. * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
    72. * @param sheetNo sheet页号,从0开始
    73. * @return List
    74. */
    75. public static List syncReadModel(String filePath, Class clazz, Integer sheetNo) {
    76. return EasyExcelFactory.read(filePath).sheet(sheetNo).head(clazz).doReadSync();
    77. }
    78. /**
    79. * 同步按模型读取(指定sheet和表头占的行数)
    80. *
    81. * @param inputStream 输入流
    82. * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
    83. * @param sheetNo sheet页号,从0开始
    84. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    85. * @return List
    86. */
    87. public static List syncReadModel(InputStream inputStream, Class clazz, Integer sheetNo, Integer headRowNum) {
    88. return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    89. }
    90. /**
    91. * 同步按模型读取(指定sheet和表头占的行数)
    92. *
    93. * @param file 文件
    94. * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
    95. * @param sheetNo sheet页号,从0开始
    96. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    97. * @return List
    98. */
    99. public static List syncReadModel(File file, Class clazz, Integer sheetNo, Integer headRowNum) {
    100. return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    101. }
    102. /**
    103. * 同步按模型读取(指定sheet和表头占的行数)
    104. *
    105. * @param filePath 文件路径
    106. * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
    107. * @param sheetNo sheet页号,从0开始
    108. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    109. * @return List
    110. */
    111. public static List syncReadModel(String filePath, Class clazz, Integer sheetNo, Integer headRowNum) {
    112. return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    113. }
    114. /**
    115. * 异步按模型读取(默认表头占一行,从第2行开始读)
    116. */
    117. /**
    118. * 异步按模型读取
    119. *
    120. * @param file 文件
    121. * @param excelListener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等
    122. * @param clazz 模型的类类型(excel数据会按该类型转换成对象)
    123. * @param sheetNo sheet页号,从0开始
    124. * @param headRowNum 表头占的行数,从0开始(如果要连表头一起读出来则传0)
    125. */
    126. public static void asyncReadModel(File file, AnalysisEventListener excelListener, Class clazz, Integer sheetNo, Integer headRowNum) {
    127. EasyExcelFactory.read(file, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    128. }
    129. /**
    130. * 异步按模型读取 文件路径
    131. */
    132. /**
    133. * 异步按模型读取 输入流
    134. *
    135. */
    136. /**
    137. * 无模板写文件 文件路径
    138. */
    139. /**
    140. * 无模板写文件 指定sheet名称
    141. */
    142. /**
    143. * 根据excel模板文件写入文件
    144. */
    145. /**
    146. * 根据excel模板文件写入文件
    147. *
    148. */
    149. /**
    150. * 按模板写文件
    151. */
    152. /**
    153. * 按模板写文件
    154. *
    155. */
    156. /**
    157. * 按模板写文件(包含某些字段)
    158. */
    159. /**
    160. * 按模板写文件(排除某些字段)
    161. */
    162. /**
    163. * 多个sheet页的数据链式写入
    164. */
    165. /**
    166. * 多个sheet页的数据链式写入(失败了会返回一个有部分数据的Excel)
    167. */
    168. /**
    169. * 同步按模型读,设置监听(指定sheet和表头占的行数)
    170. */
    171. }

    三、监听器

    监听器在我看来就是为了对读取的数据进行校验(空校验,类型校验),用于异步读取,我在上面展示了异步按模型读取文件的对应代码,其中有一个参数就是

    AnalysisEventListener excelListener
    

    这里需要我们,需要传一个AnalysisEventListener进去,我们可以根据进行自己需求的扩展AnalysisEventListener这个类,AnalysisEventListener又继承了ReadListener,我们看一下ReadListener。其中有几个方法。几个方法的用处我把自己的理解敲了上去。

    1. public interface ReadListener extends Listener {
    2. // 在转换异常获取其他异常下会调用本接口。
    3. void onException(Exception var1, AnalysisContext var2) throws Exception;
    4. //读取表头数据存在headMap中
    5. void invokeHead(Map var1, AnalysisContext var2);
    6. //读取一行一行数据到var1
    7. void invoke(T var1, AnalysisContext var2);
    8. void extra(CellExtra var1, AnalysisContext var2);
    9. //AOP思想,在完成数据解析后进行的操作
    10. void doAfterAllAnalysed(AnalysisContext var1);
    11. boolean hasNext(AnalysisContext var1);
    12. }

     在这里我提供一个自己写的代码,加深大家的理解。

    1. /**
    2. * 修改默认监听器,增加特殊需求
    3. *
    4. * @param 泛型
    5. * @author Lyle
    6. */
    7. public class ExcelListener extends AnalysisEventListener {
    8. // 保存读取的对象
    9. private final List rows = new ArrayList<>();
    10. // 日志输出
    11. private final Logger logger = LoggerFactory.getLogger(getClass());
    12. private String sheetName = "";
    13. // 获取对应类
    14. private Class headClazz;
    15. // 此map用来存储错误信息
    16. private final List errorMessage = new ArrayList<>();
    17. public ExcelListener(Class headClazz) {
    18. this.headClazz = headClazz;
    19. }
    20. /**
    21. * @param headClazz
    22. * @Description 通过class获取类字段信息
    23. */
    24. public Map getIndexNameMap(Class headClazz) throws NoSuchFieldException {
    25. Map result = new HashMap<>();
    26. Field field;
    27. Field[] fields = headClazz.getDeclaredFields(); //获取类中所有的属性
    28. for (int i = 0; i < fields.length; i++) {
    29. field = headClazz.getDeclaredField(fields[i].getName());
    30. // logger.info(String.valueOf(field));
    31. field.setAccessible(true);
    32. ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);//获取根据注解的方式获取ExcelProperty修饰的字段
    33. if (excelProperty != null) {
    34. int index = excelProperty.index(); //索引值
    35. String[] values = excelProperty.value(); //字段值
    36. StringBuilder value = new StringBuilder();
    37. for (String v : values) {
    38. value.append(v);
    39. }
    40. result.put(index, value.toString());
    41. }
    42. }
    43. return result;
    44. }
    45. @Override
    46. public void invokeHeadMap(Map headMap, AnalysisContext context) {
    47. logger.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    48. Map head = new HashMap<>();
    49. try {
    50. head = getIndexNameMap(headClazz); //通过class获取到使用@ExcelProperty注解配置的字段
    51. logger.info(String.valueOf(head));
    52. } catch (NoSuchFieldException e) {
    53. e.printStackTrace();
    54. }
    55. Set keySet = head.keySet(); //解析到的excel表头和实体配置的进行比对
    56. for (Integer key : keySet) {
    57. if (StringUtils.isEmpty(headMap.get(key))) {
    58. errorMessage.add("您上传的文件第" + (key + 1) + "列表头为空,请安照模板检查后重新上传");
    59. }
    60. if (!headMap.get(key).equals(head.get(key))) {
    61. errorMessage.add("您上传的文件第" + (key + 1) + "列表头与模板表头不一致,请检查后重新上传");
    62. }
    63. }
    64. }
    65. @Override
    66. public void invoke(T object, AnalysisContext context) {
    67. // 实际数据量比较大时,rows里的数据可以存到一定量之后进行批量处理(比如存到数据库),
    68. // 然后清空列表,以防止内存占用过多造成OOM
    69. rows.add(object);
    70. }
    71. @Override
    72. public void doAfterAllAnalysed(AnalysisContext context) {
    73. // 当前sheet的名称 编码获取类似
    74. sheetName = context.readSheetHolder().getSheetName();
    75. logger.info(sheetName);
    76. logger.info("read {} rows", rows.size());
    77. }
    78. /**
    79. * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
    80. *
    81. * @param exception 抛出异常
    82. * @param context 解析内容
    83. */
    84. @Override
    85. public void onException(Exception exception, AnalysisContext context) {
    86. logger.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
    87. if (exception instanceof ExcelDataConvertException) {
    88. ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
    89. errorMessage.add("第" + excelDataConvertException.getRowIndex() + "行,第" + (excelDataConvertException.getColumnIndex() + 1) + "列数据类型解析异常,数据为:" + excelDataConvertException.getCellData());
    90. logger.error("第{}行,第{}列数据类型解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
    91. excelDataConvertException.getColumnIndex() + 1, excelDataConvertException.getCellData());
    92. }
    93. }
    94. public List getRows() {
    95. return rows;
    96. }
    97. public Class getHeadClazz() {
    98. return headClazz;
    99. }
    100. public List getErrorMessage() {
    101. return errorMessage;
    102. }
    103. public String getSheetName() {
    104. return sheetName;
    105. }
    106. }

     四、实例演示

    首先我们需要一个模板类TestDemo,也就是我们在数据库存取的实体类。

    1. package com.swpu.component.commons.utils;
    2. import com.alibaba.excel.annotation.ExcelProperty;
    3. public class TestDemo {
    4. @ExcelProperty(value = "地层压力",index = 0)
    5. private float diCengYali;
    6. @ExcelProperty(value = "破裂压力",index = 1)
    7. private float poLieYaLi;
    8. @ExcelProperty(value = "井径扩大率",index = 2)
    9. private float jingJingKuoDa;
    10. @Override
    11. public String toString() {
    12. return "TestDemo{" +
    13. "diCengYali=" + diCengYali +
    14. ", poLieYaLi=" + poLieYaLi +
    15. ", jingJingKuoDa=" + jingJingKuoDa +
    16. '}';
    17. }
    18. public float getDiCengYali() {
    19. return diCengYali;
    20. }
    21. public void setDiCengYali(float diCengYali) {
    22. this.diCengYali = diCengYali;
    23. }
    24. public float getPoLieYaLi() {
    25. return poLieYaLi;
    26. }
    27. public void setPoLieYaLi(float poLieYaLi) {
    28. this.poLieYaLi = poLieYaLi;
    29. }
    30. public float getJingJingKuoDa() {
    31. return jingJingKuoDa;
    32. }
    33. public void setJingJingKuoDa(float jingJingKuoDa) {
    34. this.jingJingKuoDa = jingJingKuoDa;
    35. }
    36. }

    测试运行代码:

    1. public class ExcelUtilsTest {
    2. @Test
    3. @DisplayName("测试Excel导入")
    4. void testExcel() throws IOException {
    5. //待解析的文件路径
    6. File file = new File("C:\\Users\\Administrator\\Desktop\\测试.xlsx");
    7. //声明监听器
    8. ExcelListener myListener=new ExcelListener(TestDemo.class);
    9. //调用EasyExcelUtils
    10. EasyExcelUtils.asyncReadModel(file, myListener,TestDemo.class,0, 1);
    11. //获取解决出的错误信息
    12. List errorMessage = myListener.getErrorMessage();
    13. System.out.println(errorMessage);
    14. //获取监听器读到的数据,拿到的数据大家可以根据需求进行数据库操作
    15. List rows = myListener.getRows();
    16. for (Object testDemo:rows){
    17. System.out.println(testDemo.toString());
    18. }
    19. }
    20. }

     运行结果图:

     结语:

    对于一键导入Excel这一功能,我刚开始感觉确实很难实现,EasyExcel可以帮我们解决了很多复杂的if判断,理解EasyExcel的实现,灵活使用EasyExcel可以让我们的开发效率提升数倍,大家有什么不理解的可以私信,或者在下方评论里留言。我也是小白,更深入的我还是不够了解。大家可以一起交流!

  • 相关阅读:
    Python 遍历字典的若干方法
    网络热传App鉴定 |「得物」疑私删用户视频?从技术角度还原事件始末
    Pytorch从零开始实战10
    【ICPR 2021】遥感图中的密集小目标检测:Tiny Object Detection in Aerial Images
    Cholesterol-PEG-Amine,CLS-PEG-NH2,胆固醇-聚乙二醇-氨基脂质衍生物试剂供应
    str.c_str() 补充C中没有string类型的问题
    甘露糖-酰基|mannose-Hydrazide|酰基-PEG-甘露糖
    域防火墙&入站出站规则&不出网隧道上线
    2024年护网行动全国各地面试题汇总(5)作者:————LJS
    pytorch中torch.clamp()使用方法
  • 原文地址:https://blog.csdn.net/weixin_58035422/article/details/127655064