SpringBoot入门教程:上传Excel(EasyExcel)

Posted 风流 少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot入门教程:上传Excel(EasyExcel)相关的知识,希望对你有一定的参考价值。

一:简介

<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>easyexcel</artifactId>
   <version>3.1.1</version>
</dependency>

二:API

2.1 EasyExcel

public static ExcelReaderBuilder read(String pathName, ReadListener readListener);

public static ExcelReaderBuilder read(InputStream inputStream, Class head, ReadListener readListener);

2.2 ExcelReaderSheetBuilder

// 指定表头所在的行号,默认是1
public T headRowNumber(Integer headRowNumber);

public void doRead();

// 同步读(不推荐使用)
public <T> List<T> doReadSync();

2.3 ReadListener

// 1. 第一先读表头
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context);

// 2. 第二解析内容
public void invoke(SimpleUserInfo data, AnalysisContext context);
// 解析数据发生异常时会调用
public void onException(Exception exception, AnalysisContext context) throws Exception;

// 3. 第三解析额外信息
public void extra(CellExtra extra, AnalysisContext context);

// 4. 最后调用
public void doAfterAllAnalysed(AnalysisContext context);

三:案例

2.1 分页读监听器 PageReadListener

  • 使用框架提供的分页的ReadListener,一页读100条数据。

  • 通过index或者value(即列名)来将excel中的列映射到java的属性上,如果通过列名来映射当列名重复时只会读取一个,注意:官方不建议同时使用index和value同时指定

  • Model中也可以使用CellData类型,CellDate中不仅包含了值,也包含了数据类型等更多信息。

2.1.1 Model

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SimpleUserInfo 
	//  @ExcelProperty(index = 0)
	//  private String username;

    @ExcelProperty(index = 0)
    private CellData<String> usernameCellData;

    @ExcelIgnore
    private String homePage;

    @ExcelProperty(index = 1)
    private String name;

    @ExcelProperty(index = 3, converter = CustomPhoneConverter.class)
    private String phone;

    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty(index = 4)
    private Date createTime;

    @NumberFormat("#.###,##")
    @ExcelProperty(index = 5)
    private BigDecimal amount;

    @ExcelProperty(index = 6)
    private String status;

2.1.2 转换器

注意:转换器读的时候和写的时候不是重写的同一个方法。

public class CustomPhoneConverter implements Converter<String> 
    @Override
    public Class<?> supportJavaTypeKey() 
        return String.class;
    

    @Override
    public CellDataTypeEnum supportExcelTypeKey() 
        return CellDataTypeEnum.STRING;
    
    /**
     * 读的时候会调用
     */
    @Override
    public String convertToJavaData(ReadConverterContext<?> context) 
        final String originalValue = context.getReadCellData().getStringValue();
        return originalValue.substring(0, 3) + "*****" + originalValue.substring(8);
    
    /**
     * 写的时候会调用
     *
     * @return
     */
    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) 
        return new WriteCellData<>(context.getValue());
    

2.1.3 Controller

@PostMapping("/upload")
public List<SimpleUserInfo> uploadExcel(MultipartFile file) throws IOException 
    List<SimpleUserInfo> allList = new ArrayList<>();
    // 分页读取数据,一行一行的读取,默认从第一行开始读
    EasyExcel.read(file.getInputStream(), SimpleUserInfo.class, new PageReadListener<SimpleUserInfo>(dataList -> 
        allList.addAll(dataList);
    )).sheet().headRowNumber(3).doRead();
    return allList;

2.2 自定义读监听器ReadListener

2.2.1 UserService

@Slf4j
@Service
public class UserService 

    public void saveData(List<SimpleUserInfo> cachedDataList) 
        log.info("开始保存数据cachedDataList");
    

2.2.2 CustomPageReadListener

/**
 * CustomPageReadListener 不能被Spring管理,使用时需要手动new,
 * 如果内部需要使用Spring中的bean可通过构造函数传入
 */
@Slf4j
public class CustomPageReadListener implements ReadListener<SimpleUserInfo> 
    // 每批缓存数量
    private static final int BATCH_COUNT = 100;
    private List<SimpleUserInfo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    private UserService userService;

    public CustomPageReadListener(UserService userService) 
        // 这里可以传入spring的bean
        this.userService = userService;
    

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) 
        // 0=用户名, 1=姓名, 2=null, 3=手机号, 4=加入时间, 5=身价, 6=状态
        Map<Integer, String> integerStringMap = ConverterUtils.convertToStringMap(headMap, context);
        log.info("表头数据:headMap=, integerStringMap=", JSONObject.toJSONString(headMap), integerStringMap);
    

    /**
     * 解析每条数据都会调用该方法
     */
    @Override
    public void invoke(SimpleUserInfo data, AnalysisContext context) 
        log.info("解析数据:", JSONObject.toJSONString(data));
        cachedDataList.add(data);

        // 每次缓存BATCH_COUNT条数据,防止数据达到几万条还留在内容中,容易出现OOM
        if (cachedDataList.size() >= BATCH_COUNT) 
            userService.saveData(cachedDataList);
            // 清理缓存
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        
    

    /**
     * 读取额外信息:先读表头,再读数据、再读额外信息
     */
    @Override
    public void extra(CellExtra extra, AnalysisContext context) 
        switch (extra.getType()) 
            case COMMENT:
                log.info("额外信息是批注,在rowIndex:,columnIndex;,内容是:",
                        extra.getRowIndex(),
                        extra.getColumnIndex(),
                        extra.getText());
                break;
            case HYPERLINK:
                log.info("额外信息是超链接,在rowIndex:,columnIndex;,内容是:",
                        extra.getRowIndex(),
                        extra.getColumnIndex(),
                        extra.getText());
                SimpleUserInfo simpleUserInfo = cachedDataList.get(extra.getRowIndex() - context.readSheetHolder().getHeadRowNumber());
                simpleUserInfo.setHomePage(extra.getText());
                break;
            case MERGE:
                log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:,firstColumnIndex;,lastRowIndex:,lastColumnIndex:",
                        extra.getFirstRowIndex(),
                        extra.getFirstColumnIndex(),
                        extra.getLastRowIndex(),
                        extra.getLastColumnIndex());
                break;
            default:
                break;
        
    

    /**
     * 解析数据出现异常时会调用该方法(如转换异常)
     * 如果该方法不抛出异常则继续读取下一行,抛出异常则终止读取
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception 
        log.error("解析数据出现异常,但会继续解析下一行数据", exception);
        if (exception instanceof ExcelDataConvertException) 
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第行,第列,数据=解析异常", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(),
                    excelDataConvertException.getCellData());
        
    

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) 
        log.info("所有数据解析玩,最终会调用该方法:", JSONArray.toJSONString(cachedDataList));
        // 保存最后一次分页的数据
        userService.saveData(cachedDataList);
    

2.2.3 Controller

@RequestMapping("/excel")
public class EasyExcelController 

    @Autowired
    private UserService userService;

    @PostMapping("/upload")
    public void uploadExcel(MultipartFile file) throws IOException 
    	// 默认是不读取注释、超链接、合并单元格等信息,要想读必须显式设置
        EasyExcel.read(file.getInputStream(), SimpleUserInfo.class, new CustomPageReadListener(userService))
    		.extraRead(CellExtraTypeEnum.COMMENT)
            .extraRead(CellExtraTypeEnum.HYPERLINK)
            .extraRead(CellExtraTypeEnum.MERGE)
            .sheet()
            .headRowNumber(3)
            .doRead();
    
    

headMap

    "0": 
        "columnIndex": 0, 
        "dataFormatData": 
            "format": "General", 
            "index": 0
        , 
        "rowIndex": 2, 
        "stringValue": "用户名", 
        "type": 0
    , 
    "1": 
        "columnIndex": 1, 
        "dataFormatData": 
            "$ref": "$.dataFormatData"
        , 
        "rowIndex": 2, 
        "stringValue": "姓名", 
        "type": 0
    , 
    "3": 
        "columnIndex": 3, 
        "dataFormatData": 
            "$ref": "$.dataFormatData"
        , 
        "rowIndex": 2, 
        "stringValue": "手机号", 
        "type": 0
    , 
    "4": 
        "columnIndex": 4, 
        "dataFormatData": 
            "$ref": "$.dataFormatData"
        , 
        "rowIndex": 2, 
        "stringValue": "加入时间", 
        "type": 0
    , 
    "5": 
        "columnIndex": 5, 
        "dataFormatData": 
            "$ref": "$.dataFormatData"
        , 
        "rowIndex": 2, 
        "stringValue": "身价", 
        "type": 0
    , 
    "6": 
        "columnIndex": 6, 
        "dataFormatData": 
            "$ref": "$.dataFormatData"
        , 
        "rowIndex": 2, 
        "stringValue": "状态", 
        "type": 0
    


integerStringMap=0=用户名, 1=姓名, 2=null, 3=手机号, 4=加入时间, 5=身价, 6=状态
[
    
        "amount": 6666666, 
        "createTime": 1663913277003, 
        "homePage": "https://blog.csdn.net/vbirdbest", 
        "name": "小龙女", 
        "phone": "166*****666", 
        "status": "正常", 
        "usernameCellData": 
            "data": "xiaolongnv", 
            "dataFormatData": 
                "format": "General", 
                "index": 0
            , 
            "stringValue": "xiaolongnv", 
            "type": 0
        
    , 
    
        "amount": 8888888, 
        "createTime": 1663913277003, 
        "homePage": "https://blog.csdn.net/vbirdbest", 
        "name": "任莹莹", 
        "phone": "188*****888", 
        "status": "锁定", 
        "usernameCellData": 
            "data": "renyingying", 
            "dataFormatData": 
                "format": "General", 
                "index": 0
            , 
            "stringValue": "renyingying", 
            "type": 0
        
    
]

2.3 读多个Sheet

try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) 
    ReadSheet readSheet1 = EasyExcel.readSheet(0)
    	.head(SimpleUserInfo.class)
    	.registerReadListener(new CustomPageReadListener(userService))
    	.headRowNumber(3)
    	.build();
    ReadSheet readSheet2 = EasyExcel.readSheet(0)
    	.head(SimpleUserInfo2.class)
    	.registerReadListener(new CustomPageReadListener2(userService2))
    	.headRowNumber(3)
    	.build();
    excelReader.read(readSheet1, readSheet2);

2.4 读没有表头

@Slf4j
public class CustomAnalysisEventListener extends AnalysisEventListener<Map<Integer, String>> 
    private 以上是关于SpringBoot入门教程:上传Excel(EasyExcel)的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot入门教程:上传Excel(EasyExcel)

springboot 实现excel文件上传

Vue + SpringBoot 上传 Excel 文件

springboot实现上传并解析Excel

SpringBoot——快速整合EasyExcel实现Excel的上传下载

SpringBoot——快速整合EasyExcel实现Excel的上传下载