51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

SpringBoot整合EasyExcel实现复杂Excel表格的导入&导出功能

转载自:SpringBoot整合EasyExcel实现复杂Excel表格的导入&导出功能

前言 {#前言}

在后端管理系统的开发中,经常有导出当前表格数据的功能,有些前端表格组件可以直接做到,但是不够灵活。因为前端拿到的数据始终是经过处理的,如果想拿到原版数据,必须后端处理。 另外,Excel的导入同样也很常见,对于数据的处理也是一件麻烦的事情,常见的工具包如Apache POI可以帮我们简化这些流程,但是它也有一些缺点,比如要创建的类实在是太多了。

那么,除了使用Apache POI包,还有没有其他的选择?当然有!*这里我给大家推荐一款非常简单且容易上手的开源组件:Alibaba EasyExcel

组件介绍 {#组件介绍}

首先放出官网地址,欢迎大家star(目前已经24K): 点我跳转

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本)

Alibaba EasyExcel的核心类是EasyExcel

/**
 * 最简单的读
 * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
 * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
 * <p>3. 直接读即可
 */
@Test
public void simpleRead() {
    String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
/**
 * 最简单的写
 * <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
 * <p>2. 直接写即可
 */
@Test
public void simpleWrite() {
    String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
    // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    // 如果这里想使用03 则 传入excelType参数即可
    EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}

尝试一下 {#尝试一下}

项目基本信息 {#项目基本信息}

项目结构 {#项目结构}

pom.xml {#pomxml}

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SpringBoot-easyexcel</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot-easyexcel</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <span class="hljs-comment">&lt;!-- Web组件 --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- easyexcel --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.alibaba<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>easyexcel<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.1.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- commons-lang3 --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.commons<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>commons-lang3<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.8.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- lombok插件 --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.projectlombok<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>lombok<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.18.12<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>



</project>`
`

文件导出 {#文件导出}

先展示一下效果。

单个sheet&表头合并 {#单个sheet表头合并}

多个sheet导出 {#多个sheet导出}

下面是导出相关的类代码

ExportController.java {#exportcontrollerjava}

package com.example.springbooteasyexcel.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.example.springbooteasyexcel.data.Mock;
import com.example.springbooteasyexcel.sheet.CitySheet;
import com.example.springbooteasyexcel.sheet.CompanySheet;
import com.example.springbooteasyexcel.sheet.UserSheet;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;


@RestController
@RequestMapping("/export")
public class ExportController {


    <span class="hljs-comment">/**
     * <span class="hljs-doctag">@param</span> response
     * <span class="hljs-doctag">@url</span> &lt;a&gt;http://localhost:8080/export/test1&lt;/a&gt;
     * 在Excel中写入单个sheet
     */</span>
    <span class="hljs-meta">@RequestMapping("/test1")</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">test1</span><span class="hljs-params">(HttpServletResponse response)</span> {
        <span class="hljs-comment">//从HttpServletResponse中获取OutputStream输出流</span>
        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// 设置响应类型</span>
            response.setContentType(<span class="hljs-string">"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"</span>);
            <span class="hljs-comment">// 设置编码格式</span>
            response.setCharacterEncoding(<span class="hljs-string">"utf-8"</span>);
            <span class="hljs-comment">// 设置URLEncoder.encode 防止中文乱码</span>
            <span class="hljs-type">String</span> <span class="hljs-variable">fileName</span> <span class="hljs-operator">=</span> URLEncoder.encode(<span class="hljs-string">"用户信息表"</span>, <span class="hljs-string">"UTF-8"</span>).replaceAll(<span class="hljs-string">"\\+"</span>, <span class="hljs-string">"%20"</span>);
            <span class="hljs-comment">// 设置响应头</span>
            response.setHeader(<span class="hljs-string">"Content-disposition"</span>, <span class="hljs-string">"attachment;filename*=utf-8''"</span> + fileName + <span class="hljs-string">".xlsx"</span>);
            <span class="hljs-comment">// 写出Excel</span>
            EasyExcel.write(response.getOutputStream(), UserSheet.class).inMemory(<span class="hljs-literal">true</span>).sheet(<span class="hljs-string">"用户信息表"</span>).doWrite(Mock.userList());
        } <span class="hljs-keyword">catch</span> (IOException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">"数据或文件损坏,无法下载"</span>);
        }
    }

    <span class="hljs-comment">/**
     * 在Excel中写入多个sheet
     *
     * <span class="hljs-doctag">@url</span> &lt;a&gt;http://localhost:8080/export/test2&lt;/a&gt;
     */</span>
    <span class="hljs-meta">@RequestMapping("/test2")</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">test2</span><span class="hljs-params">(HttpServletResponse response)</span> <span class="hljs-keyword">throws</span> Exception {
        <span class="hljs-comment">// 设置响应类型</span>
        response.setContentType(<span class="hljs-string">"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"</span>);
        <span class="hljs-comment">// 设置编码格式</span>
        response.setCharacterEncoding(<span class="hljs-string">"utf-8"</span>);
        <span class="hljs-comment">// 设置URLEncoder.encode 防止中文乱码</span>
        <span class="hljs-type">String</span> <span class="hljs-variable">fileName</span> <span class="hljs-operator">=</span> URLEncoder.encode(<span class="hljs-string">"信息表"</span>, <span class="hljs-string">"UTF-8"</span>).replaceAll(<span class="hljs-string">"\\+"</span>, <span class="hljs-string">"%20"</span>);
        <span class="hljs-comment">// 设置响应头</span>
        response.setHeader(<span class="hljs-string">"Content-disposition"</span>, <span class="hljs-string">"attachment;filename*=utf-8''"</span> + fileName + <span class="hljs-string">".xlsx"</span>);
        <span class="hljs-comment">// 多个sheet的输出需要使用ExcelWriter类,这里想要下载成功,需要输出到OutputStream中</span>
        <span class="hljs-keyword">try</span> (<span class="hljs-type">ExcelWriter</span> <span class="hljs-variable">excelWriter</span> <span class="hljs-operator">=</span> EasyExcel.write(response.getOutputStream()).inMemory(<span class="hljs-literal">true</span>).build()) {
            <span class="hljs-comment">// 创建用户信息表的sheet,写入用户信息数据,1代表sheet的位置是第一个</span>
            <span class="hljs-type">WriteSheet</span> <span class="hljs-variable">userInfoSheet</span> <span class="hljs-operator">=</span> EasyExcel.writerSheet(<span class="hljs-number">0</span>, <span class="hljs-string">"用户信息表"</span>).head(UserSheet.class).build();
            excelWriter.write(Mock.userList(), userInfoSheet);
            <span class="hljs-comment">// 创建城市信息表的sheet,写入城市信息数据,2代表sheet的位置是第二个</span>
            <span class="hljs-type">WriteSheet</span> <span class="hljs-variable">cityInfoSheet</span> <span class="hljs-operator">=</span> EasyExcel.writerSheet(<span class="hljs-number">1</span>, <span class="hljs-string">"城市信息表"</span>).head(CitySheet.class).build();
            excelWriter.write(Mock.cityList(), cityInfoSheet);
            <span class="hljs-comment">// 创建公司信息表的sheet,写入公司信息数据,3代表sheet的位置是第三个</span>
            <span class="hljs-type">WriteSheet</span> <span class="hljs-variable">companyInfoSheet</span> <span class="hljs-operator">=</span> EasyExcel.writerSheet(<span class="hljs-number">2</span>, <span class="hljs-string">"公司信息表"</span>).head(CompanySheet.class).build();
            excelWriter.write(Mock.companyList(), companyInfoSheet);
        }
    }



`}
`

Mock.java {#mockjava}

以下数据均来自于网络,如有侵权,请联系删除

package com.example.springbooteasyexcel.data;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.RichTextStringData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.example.springbooteasyexcel.sheet.CitySheet;
import com.example.springbooteasyexcel.sheet.CompanySheet;
import com.example.springbooteasyexcel.sheet.UserSheet;
import org.apache.poi.ss.usermodel.IndexedColors;


import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;


public class Mock {


    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List&lt;UserSheet&gt; <span class="hljs-title function_">userList</span><span class="hljs-params">()</span> {
        List&lt;UserSheet&gt; list = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(<span class="hljs-number">10</span>);
        list.add(UserSheet.builder().userId(<span class="hljs-number">001L</span>).userName(<span class="hljs-string">"张三"</span>).userPhone(<span class="hljs-string">"11112223123"</span>).userEmail(<span class="hljs-string">"zhansan@163.com"</span>).userAddress(<span class="hljs-string">"北京朝阳区"</span>).gender(buildCellData(<span class="hljs-string">"男"</span>)).registerTime(Calendar.getInstance().getTime()).build());
        list.add(UserSheet.builder().userId(<span class="hljs-number">002L</span>).userName(<span class="hljs-string">"李四"</span>).userPhone(<span class="hljs-string">"11112223123"</span>).userEmail(<span class="hljs-string">"lisi@qq.com"</span>).userAddress(<span class="hljs-string">"南京玄武门"</span>).gender(buildCellData(<span class="hljs-string">"女"</span>)).registerTime(Calendar.getInstance().getTime()).build());
        list.add(UserSheet.builder().userId(<span class="hljs-number">003L</span>).userName(<span class="hljs-string">"王五"</span>).userPhone(<span class="hljs-string">"11112223123"</span>).userEmail(<span class="hljs-string">"wangwu@google.com"</span>).userAddress(<span class="hljs-string">"杭州未来科技城"</span>).gender(buildCellData(<span class="hljs-string">"男"</span>)).registerTime(Calendar.getInstance().getTime()).build());
        list.add(UserSheet.builder().userId(<span class="hljs-number">004L</span>).userName(<span class="hljs-string">"赵六"</span>).userPhone(<span class="hljs-string">"11112223123"</span>).userEmail(<span class="hljs-string">"zhaoliu@baidu.com"</span>).userAddress(<span class="hljs-string">"上海徐家汇"</span>).gender(buildCellData(<span class="hljs-string">"女"</span>)).registerTime(Calendar.getInstance().getTime()).build());
        <span class="hljs-keyword">return</span> list;
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> WriteCellData&lt;String&gt; <span class="hljs-title function_">buildCellData</span><span class="hljs-params">(String gender)</span> {
        <span class="hljs-comment">// 设置单个单元格多种样式</span>
        WriteCellData&lt;String&gt; cellData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WriteCellData</span>&lt;&gt;();
       	<span class="hljs-comment">// 设置单个单元格的填充类型</span>
        cellData.setType(CellDataTypeEnum.RICH_TEXT_STRING);
        
        <span class="hljs-type">RichTextStringData</span> <span class="hljs-variable">richTextStringData</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RichTextStringData</span>();
        cellData.setRichTextStringDataValue(richTextStringData);
        richTextStringData.setTextString(gender);
        <span class="hljs-type">WriteFont</span> <span class="hljs-variable">writeFont</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">WriteFont</span>();
        <span class="hljs-keyword">if</span> (<span class="hljs-string">"男"</span>.equalsIgnoreCase(gender)) {
        	<span class="hljs-comment">//设置颜色为红色</span>
            writeFont.setColor(IndexedColors.RED.getIndex());
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-string">"女"</span>.equalsIgnoreCase(gender)) {
        	<span class="hljs-comment">//设置颜色为绿色</span>
            writeFont.setColor(IndexedColors.GREEN.getIndex());
        }
        <span class="hljs-comment">//应用颜色字体</span>
        richTextStringData.applyFont(writeFont);
        <span class="hljs-keyword">return</span> cellData;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List&lt;CitySheet&gt; <span class="hljs-title function_">cityList</span><span class="hljs-params">()</span> {
        List&lt;CitySheet&gt; list = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(<span class="hljs-number">10</span>);
        list.add(CitySheet.builder().cityName(<span class="hljs-string">"杭州市"</span>).cityDesc(<span class="hljs-string">"杭州市一般指杭州。 杭州,简称"杭",古称临安、钱塘,浙江省辖地级市、省会、副省级市、特大城市、国务院批复确定的浙江省经济、文化、科教中心,长江三角洲中心城市之一,环杭州湾大湾区核心城市、G60科创走廊中心城市。"</span>).build());
        list.add(CitySheet.builder().cityName(<span class="hljs-string">"合肥市"</span>).cityDesc(<span class="hljs-string">"合肥市一般指合肥。 合肥,简称"庐"或"合",古称庐州、庐阳、合淝,安徽省辖地级市、省会,是合肥都市圈中心城市,国务院批复确定的中国长三角城市群副中心城市,全国四大科教基地、现代制造业基地和综合交通枢纽。"</span>).build());
        list.add(CitySheet.builder().cityName(<span class="hljs-string">"武汉市"</span>).cityDesc(<span class="hljs-string">"武汉市一般指武汉。 武汉,简称"汉",别称江城,是湖北省省会,中部六省唯一的副省级市,超大城市,中国中部地区的中心城市,全国重要的工业基地、科教基地和综合交通枢纽,联勤保障部队机关驻地。"</span>).build());
        list.add(CitySheet.builder().cityName(<span class="hljs-string">"深圳市"</span>).cityDesc(<span class="hljs-string">"深圳市一般指深圳。 深圳,简称"深",别称鹏城,广东省辖地级市,是广东省副省级市,国家计划单列市,超大城市,国务院批复确定的中国经济特区、全国性经济中心城市、国际化城市、科技创新中心、区域金融中心、商贸物流中心。"</span>).build());
        <span class="hljs-keyword">return</span> list;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List&lt;CompanySheet&gt; <span class="hljs-title function_">companyList</span><span class="hljs-params">()</span> {
        List&lt;CompanySheet&gt; list = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;(<span class="hljs-number">10</span>);
        list.add(CompanySheet.builder().companyName(<span class="hljs-string">"阿里巴巴"</span>).companyBoss(<span class="hljs-string">"马云"</span>).companyBase(<span class="hljs-string">"杭州市"</span>).companyDesc(<span class="hljs-string">"阿里巴巴集团经营多项业务,另外也从关联公司的业务和服务中取得经营商业生态系统上的支援。业务和关联公司的业务包括:淘宝网、天猫、聚划算、全球速卖通、阿里巴巴国际交易市场、1688、阿里妈妈、阿里云、蚂蚁集团 [408]  、菜鸟网络等。"</span>).build());
        list.add(CompanySheet.builder().companyName(<span class="hljs-string">"字节跳动"</span>).companyBoss(<span class="hljs-string">"张一鸣"</span>).companyBase(<span class="hljs-string">"北京市"</span>).companyDesc(<span class="hljs-string">"字节跳动的全球化布局始于2015年 [3]  ,"技术出海"是字节跳动全球化发展的核心战略 [4]  ,其旗下产品有今日头条、西瓜视频、抖音、头条百科、皮皮虾、懂车帝、悟空问答等。"</span>).build());
        list.add(CompanySheet.builder().companyName(<span class="hljs-string">"腾讯"</span>).companyBoss(<span class="hljs-string">"马化腾"</span>).companyBase(<span class="hljs-string">"深圳市"</span>).companyDesc(<span class="hljs-string">"社交和通信服务QQ及微信/WeChat、社交网络平台QQ空间、腾讯游戏旗下QQ游戏平台、门户网站腾讯网、腾讯新闻客户端和网络视频服务腾讯视频等。"</span>).build());
        list.add(CompanySheet.builder().companyName(<span class="hljs-string">"百度"</span>).companyBoss(<span class="hljs-string">"李彦宏"</span>).companyBase(<span class="hljs-string">"北京市"</span>).companyDesc(<span class="hljs-string">"百度(Baidu)是拥有强大互联网基础的领先AI公司。百度愿景是:成为最懂用户,并能帮助人们成长的全球顶级高科技公司。"</span>).build());
        <span class="hljs-keyword">return</span> list;
    }



`}
`

CitySheet.java {#citysheetjava}

package com.example.springbooteasyexcel.sheet;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Builder;
import lombok.Data;


@Data
@Builder
public class CitySheet {


    <span class="hljs-meta">@ExcelProperty(value = "城市名称", index = 0)</span>
    <span class="hljs-meta">@ColumnWidth(10)</span>
    <span class="hljs-keyword">private</span> String cityName;

    <span class="hljs-meta">@ExcelProperty(value = "城市介绍", index = 1)</span>
    <span class="hljs-meta">@ColumnWidth(60)</span>
    <span class="hljs-keyword">private</span> String cityDesc;



`}
`

CompanySheet.java {#companysheetjava}

package com.example.springbooteasyexcel.sheet;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Builder;
import lombok.Data;


@Data
@Builder
public class CompanySheet {


    <span class="hljs-meta">@ExcelProperty(value = "公司名称", index = 0)</span>
    <span class="hljs-meta">@ColumnWidth(10)</span>
    <span class="hljs-keyword">private</span> String companyName;

    <span class="hljs-meta">@ExcelProperty(value = "公司创始人", index = 1)</span>
    <span class="hljs-meta">@ColumnWidth(10)</span>
    <span class="hljs-keyword">private</span> String companyBoss;

    <span class="hljs-meta">@ExcelProperty(value = "公司总基地", index = 2)</span>
    <span class="hljs-meta">@ColumnWidth(10)</span>
    <span class="hljs-keyword">private</span> String companyBase;

    <span class="hljs-meta">@ExcelProperty(value = "公司简介", index = 3)</span>
    <span class="hljs-meta">@ColumnWidth(50)</span>
    <span class="hljs-keyword">private</span> String companyDesc;



`}
`

UserSheet.java {#usersheetjava}

package com.example.springbooteasyexcel.sheet;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.metadata.data.WriteCellData;
import lombok.Builder;
import lombok.Data;


import java.util.Date;


@Data
@Builder
public class UserSheet {


    <span class="hljs-meta">@ExcelProperty(value = "用户ID", index = 0)</span>
    <span class="hljs-meta">@ColumnWidth(10)</span>
    <span class="hljs-keyword">private</span> Long userId;

    <span class="hljs-meta">@ExcelProperty(value = "用户名称", index = 1)</span>
    <span class="hljs-meta">@ColumnWidth(10)</span>
    <span class="hljs-keyword">private</span> String userName;

    <span class="hljs-meta">@ExcelProperty(value = {"基本信息", "手机号码"}, index = 2)</span>
    <span class="hljs-meta">@ColumnWidth(20)</span>
    <span class="hljs-keyword">private</span> String userPhone;

    <span class="hljs-meta">@ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3)</span>
    <span class="hljs-meta">@ColumnWidth(20)</span>
    <span class="hljs-keyword">private</span> String userEmail;

    <span class="hljs-meta">@ExcelProperty(value = {"基本信息", "地址"}, index = 4)</span>
    <span class="hljs-meta">@ColumnWidth(20)</span>
    <span class="hljs-keyword">private</span> String userAddress;

    <span class="hljs-meta">@ExcelProperty(value = "注册时间", index = 5)</span>
    <span class="hljs-meta">@ColumnWidth(20)</span>
    <span class="hljs-keyword">private</span> Date registerTime;

    <span class="hljs-meta">@ExcelProperty(value = "性别,男:红色/女:绿色")</span>
    <span class="hljs-meta">@ColumnWidth(30)</span>
    <span class="hljs-keyword">private</span> WriteCellData&lt;String&gt; gender;

    <span class="hljs-comment">/**
     * 忽略这个字段
     */</span>
    <span class="hljs-meta">@ExcelIgnore</span>
    <span class="hljs-keyword">private</span> Integer age;



`}
`

SpringBootEasyexcelApplication.java {#springbooteasyexcelapplicationjava}

package com.example.springbooteasyexcel;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class SpringBootEasyexcelApplication {


    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
        SpringApplication.run(SpringBootEasyexcelApplication.class, args);
    }



`}
`

文件导入 {#文件导入}

Excel导入到数据库,我用一张流程图展示一下如何使用EasyExcel进行导入。

EasyExcel有一个AnalysisEventListener,可以自定义一个Listener继承AnalysisEventListener,里面有一个invoke方法,每条数据都会进入这个方法。我们可以在这里做校验、存储、抛异常等动作,EasyExcel将这些都流程化了,写起代码来非常舒服。当然也有一些点需要注意下,比如自定义Listener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去。

下面是导入相关的一些类代码,最关键的是就是UserExcelReadListener这个,大家可以仔细看看。

UserData.java {#userdatajava}

package com.example.springbooteasyexcel.read;

import java.util.Date;


import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.metadata.data.WriteCellData;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;


@Data
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class UserData {


    <span class="hljs-keyword">private</span> Long userId;

    <span class="hljs-keyword">private</span> String userName;

    <span class="hljs-keyword">private</span> Integer age;

    <span class="hljs-keyword">private</span> String userPhone;

    <span class="hljs-keyword">private</span> String userEmail;

    <span class="hljs-keyword">private</span> String userAddress;

    <span class="hljs-keyword">private</span> Date registerTime;

    <span class="hljs-keyword">private</span> String gender;



`}
`

UserExcelReadListener.java {#userexcelreadlistenerjava}

package com.example.springbooteasyexcel.read;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;


import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.util.StringUtils;


import lombok.extern.slf4j.Slf4j;


/**

`
`
* `
  `有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  */
  @Slf4j
  public class UserExcelReadListener extends AnalysisEventListener<UserData> {
  `
  `

  /**
  `
  `

  * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
    */
    private static final int BATCH_COUNT = 100;


  `
  `

  /**
  `
  `

  * 创建一个Pattern对象,使用正则表达式校验手机号格式
    */
    private static final Pattern PHONE_REGEX = Pattern.compile("^1[0-9]{10}$");


  `
  `

  /**
  `
  `

  * 缓存的数据
    */
    private List<UserData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);


  `
  `

  /**
  `
  `

  * 错误信息列表
    */
    private final List<String> errorMsgList = new ArrayList<>(BATCH_COUNT);


  `
  `

  @Override
  public void invoke(UserData userData, AnalysisContext analysisContext) {
  log.info("解析到一条数据:{}", userData);
  int rowIndex = analysisContext.readRowHolder().getRowIndex();
  String name = userData.getUserName();
  String phone = userData.getUserPhone();
  String gender = userData.getGender();
  String email = userData.getUserEmail();
  Integer age = userData.getAge();
  String address = userData.getUserAddress();
  // 只有全部校验通过的对象才能被添加到下一步
  if (nameValid(rowIndex, name) && phoneValid(rowIndex, phone) && genderValid(rowIndex, gender) &&
  emailValid(rowIndex, email) && ageValid(rowIndex, age) && addressValid(rowIndex, address)) {
  cachedDataList.add(userData);
  }
  // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
  if (cachedDataList.size() >= BATCH_COUNT) {
  // to saveData();
  // 存储完成清理 list
  cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  }
  }
  `
  `

  @Override
  public void doAfterAllAnalysed(AnalysisContext analysisContext) {
  log.info("所有数据解析完成!全部校验通过的数据有{}条", cachedDataList.size());
  // 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();
  // todo saveData();
  `
  `

  }
  `
  `

  @Override
  public void onException(Exception exception, AnalysisContext context) throws Exception {
  if (exception instanceof RuntimeException) {
  throw exception;
  }
  int index = context.readRowHolder().getRowIndex() + 1;
  errorMsgList.add("第" + index + "行解析错误");
  }
  `
  `

  @Override
  public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
  int totalRows = context.readSheetHolder().getApproximateTotalRowNumber() - 1;
  int maxNum = 2000;
  if (totalRows > maxNum) {
  errorMsgList.add("数据量过大,单次最多上传2000条");
  throw new RuntimeException("数据量过大,单次最多上传2000条");
  }
  }
  `
  `

  public List<String> getErrorMsgList() {
  return errorMsgList;
  }
  `
  `

  /**
  `
  `

  * 名称的校验


  * 


  * @param rowIndex 行数


  * @param name     名称
    */
    private Boolean nameValid(Integer rowIndex, String name) {
    if (StringUtils.isBlank(name)) {
    errorMsgList.add("第" + rowIndex + "行,'姓名'不能为空");
    return Boolean.FALSE;
    }
    return Boolean.TRUE;
    }


  `
  `

  private Boolean phoneValid(int rowIndex, String phone) {
  if (StringUtils.isBlank(phone)) {
  errorMsgList.add("第" + rowIndex + "行,'手机号'不能为空");
  return Boolean.FALSE;
  }
  return Boolean.TRUE;
  }
  `
  `

  /**
  `
  `

  * 性别的校验


  * 


  * @param rowIndex 行数


  * @param gender   性别
    */
    private Boolean genderValid(int rowIndex, String gender) {
    if (StringUtils.isBlank(gender)) {
    errorMsgList.add("第" + rowIndex + "行,'性别'不能为空");
    return Boolean.FALSE;
    }
    return Boolean.TRUE;
    }


  `
  `

  /**
  `
  `

  * 地址校验


  * 


  * @param rowIndex 行数


  * @param address  地址
    */
    private Boolean addressValid(int rowIndex, String address) {
    // 校验地址是否为空
    if (StringUtils.isBlank(address)) {
    errorMsgList.add("第 " + rowIndex + " 行,'地址'不能为空");
    return Boolean.FALSE;
    }
    return Boolean.TRUE;
    }


  `
  `

  /**
  `
  `

  * 年龄的校验


  * 


  * @param rowIndex 行数


  * @param age      年龄
    */
    private Boolean ageValid(int rowIndex, Integer age) {
    // 校验年龄是否为空
    if (Objects.isNull(age)) {
    errorMsgList.add("第 " + rowIndex + " 行'年龄'不能为空");
    return Boolean.FALSE;
    }
    return Boolean.TRUE;
    }


  `
  `

  /**
  `
  ``
  `
  * 邮箱的校验


  * 


  * @param rowIndex 行数



  * @param` email    邮箱
    */``
    `private` Boolean `emailValid(int rowIndex, String email)` {
    `// 校验邮箱是否为空`
    `if` (StringUtils.isBlank(email)) {
    errorMsgList.add(`"第 "` + rowIndex + `" 行'邮箱'不能为空"`);
    `return` Boolean.FALSE;
    }
    `return` Boolean.TRUE;
    }
    }
    `

package com.example.springbooteasyexcel.read;

import com.alibaba.excel.EasyExcel;

public class ReadExcelTest {

    public static void main(String[] args) {
        UserExcelReadListener userExcelReadListener = new UserExcelReadListener();
        EasyExcel.read("用户信息表.xlsx", UserData.class, userExcelReadListener).sheet().doRead();
        System.out.println(userExcelReadListener.getErrorMsgList());
    }
}

执行结果 {#执行结果}

总结一下 {#总结一下}

1、Alibaba EasyExcel不仅支持写Excel,还支持读Excel和填充Excel,有兴趣的话可以自己去研究,官网地址已经贴在上面了,我这里只做一个引路的。

2、常用注解有三个@ExcelProperty@ColumnWidth@ExcelIgnore。 (1)@ExcelProperty不仅确定表头,还可以合并行,用法如下:

@ExcelProperty(value = {"基本信息", "手机号码"}, index = 2)
@ColumnWidth(20)
private String userPhone;

@ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3)
@ColumnWidth(20)
private String userEmail;

@ExcelProperty(value = {"基本信息", "地址"}, index = 4)`
`@ColumnWidth(20)`
`private` String userAddress;
`

效果如下:在这里插入图片描述

(2)@ColumnWidth主要是控制列宽

(3)@ExcelIgnore忽略不需要输出的字段 3、写有两种形式 (1)写到文件

/**
     * 最简单的写
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 直接写即可
     */
@Test
public void simpleWrite() {
    // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入

    <span class="hljs-comment">// 写法1 JDK8+</span>
    <span class="hljs-comment">// since: 3.0.0-beta1</span>
    <span class="hljs-type">String</span> <span class="hljs-variable">fileName</span> <span class="hljs-operator">=</span> TestFileUtil.getPath() + <span class="hljs-string">"simpleWrite"</span> + System.currentTimeMillis() + <span class="hljs-string">".xlsx"</span>;
    <span class="hljs-comment">// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭</span>
    <span class="hljs-comment">// 如果这里想使用03 则 传入excelType参数即可</span>
    EasyExcel.write(fileName, DemoData.class)
        .sheet(<span class="hljs-string">"模板"</span>)
        .doWrite(() -&gt; {
            <span class="hljs-comment">// 分页查询数据</span>
            <span class="hljs-keyword">return</span> data();
        });

    <span class="hljs-comment">// 写法2</span>
    fileName = TestFileUtil.getPath() + <span class="hljs-string">"simpleWrite"</span> + System.currentTimeMillis() + <span class="hljs-string">".xlsx"</span>;
    <span class="hljs-comment">// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭</span>
    <span class="hljs-comment">// 如果这里想使用03 则 传入excelType参数即可</span>
    EasyExcel.write(fileName, DemoData.class).sheet(<span class="hljs-string">"模板"</span>).doWrite(data());

    <span class="hljs-comment">// 写法3</span>
    fileName = TestFileUtil.getPath() + <span class="hljs-string">"simpleWrite"</span> + System.currentTimeMillis() + <span class="hljs-string">".xlsx"</span>;
    <span class="hljs-comment">// 这里 需要指定写用哪个class去写</span>
    <span class="hljs-keyword">try</span> (<span class="hljs-type">ExcelWriter</span> <span class="hljs-variable">excelWriter</span> <span class="hljs-operator">=</span> EasyExcel.write(fileName, DemoData.class).build()) {
        <span class="hljs-type">WriteSheet</span> <span class="hljs-variable">writeSheet</span> <span class="hljs-operator">=</span> EasyExcel.writerSheet(<span class="hljs-string">"模板"</span>).build();
        excelWriter.write(data(), writeSheet);
    }



`}
`

(2)写到Web流,这里的ContentType和CharacterEncoding不要乱码,否则很容易乱码或者文件损坏

/**
     * 文件下载(失败了会返回一个有部分数据的Excel)
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DownloadData}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
     */
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {
    // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
}

(3)用好自定义Listener可以很好的优化代码,并且逻辑也可以变得更清晰。

赞(3)
未经允许不得转载:工具盒子 » SpringBoot整合EasyExcel实现复杂Excel表格的导入&导出功能