day53_vue+easyexcel+springboot

news2024/9/25 15:22:31

EasyExcel

一、初识EasyExcel

1. Apache POI

先说POI,有过报表导入导出经验的同学,应该听过或者使用。

Apache POI是Apache软件基金会的开源函式库,提供跨平台的Java API实现Microsoft Office格式档案读写。但是存在如下一些问题:

1.1 学习使用成本较高

对POI有过深入了解的才知道原来POI还有SAX模式(Dom解析模式)。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。

想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。

POI的SAX模式的API可以一定程度的解决一些内存溢出的问题,但是POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大,一个3M的Excel用POI的SAX解析,依然需要100M左右内存。

1.2 POI的内存消耗较大

大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc。

总体上来说,简单写法重度依赖内存,复杂写法学习成本高。

特点
  1. 功能强大

  2. 代码书写冗余繁杂

  3. 读写大文件耗费内存较大,容易OOM

2. EasyExcel

2.1 重写了POI对07版Excel的解析
  • EasyExcel重写了POI对07版Excel的解析,可以把内存消耗从100M左右降低到10M以内,并且再大的Excel不会出现内存溢出,03版仍依赖POI的SAX模式。

  • 下图为64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)

img

  • 在上层做了模型转换的封装,让使用者更加简单方便
特点
  1. 在数据模型层面进行了封装,使用简单
  2. 重写了07版本的Excel的解析代码,降低内存消耗,能有效避免OOM
  3. 只能操作Excel
  4. 不能读取图片

二、快速入门–QuickStart

0、导入依赖坐标

<!-- EasyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.6</version>
</dependency>
<!-- lombok 优雅编程 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
</dependency>
<!-- junit单元测试:可以让每个方法独立运行,不要一直改主方法 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

导入easyexcel-2.1.6坐标的时候,已依赖传递导入poi-3.17的POI。

1、最简单的读

1.1、需求、准备工作
/**
 * 需求:单实体导入(从磁盘将文件导入到应用程序)
 * 导入Excel学员信息到系统。
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */

学员信息.xlsx

image-20230618174203589

学生姓名学生出生日期学生性别
学号002020-03-16
学号012020-03-16
学号022020-03-16
学号032020-03-16
学号042020-03-16
学号052020-03-16
学号062020-03-16
学号072020-03-16
学号082020-03-16
学号092020-03-16
1.2、编写导出数据的实体类
package com.taotie.test;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
// 基于lombok
@Data
@NoArgsConstructor
@AllArgsConstructor
@HeadRowHeight(20) // 指定列头行高
@ColumnWidth(20) // 指定列宽
public class Student {
    /**
     * 学生姓名
     */
    @ExcelProperty(value = "学生姓名", index = 0)
    private String name;
    /**
     * 学生性别
     */
    @ExcelProperty(value = "学生性别", index = 2)
    private String gender;

    /**
     * 学生出生日期
     */
    @ExcelProperty(value = "学生出生日期", index = 1)
    private Date birthday;
    /**
     * id
     */
    // @ExcelProperty(value = "编号",index = 3)
    @ExcelIgnore // 忽略,不读取
    private String id;
}
注解: 文章后面有详解
1.3、 读取Excel文件

调用EasyExcelAPI读取的Excel文件的测试类StudentReadDemo

package com.taotie.test;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import org.junit.Test;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
public class TestEasyExcel {

    /**
     * 测试读取数据
     */
    @Test
    public void testRead() {
        // 读取文件,读取完之后会自动关闭
        /**
         * 参数1:pathName  文件路径;"d:\\学员信息.xls"
         * 参数2:head		 每行数据对应的实体;Student.class
         * 参数3:readListener	读监听器,每读一样就会调用一次该监听器的invoke方法
         * 参数4:sheet方法参数: 工作表的顺序号(从0开始)或者工作表的名字,不传默认为0
         */
        // // 封装工作簿对象
        // ExcelReaderBuilder workBook = EasyExcel.read
        //         ("E:\\学员信息.xlsx",
        //                 Student.class,
        //                 new StudentReadListener( ));
        // // 封装工作表
        // ExcelReaderSheetBuilder sheet1 = workBook.sheet( );
        // // 读取
        // sheet1.doRead( );
        
        // 最简单的写法
        EasyExcel.read("E:\\学员信息.xlsx",Student.class,new StudentReadListener()).sheet().doRead();
    }
}

读取Excel的监听器,用于处理读取产生的数据

package com.taotie.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.taotie.domain.Student;

/**
 * @Author Vsunks.v
 * @Description:
 */
public class StudentReadListener extends AnalysisEventListener<Student> {
    // 每读一行,会调用该invoke方法一次
    @Override
    public void invoke(Student data, AnalysisContext context) {
        System.out.println("data = " + data);
        log.info(data + "保存成功");
    }

    // 全部读完之后,会调用该方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // TODO......
    }
}

2、最简单的写

2.1 需求、准备工作
/**
 * 需求:单实体导出(从应用程序将文件导入到磁盘)
 * 导出多个学生对象到Excel表格
 * 包含如下列:姓名、性别、出生日期
 * 模板详见:学员信息.xlsx
 */
2.2、编写导出数据的实体
// 还是上个实体类...
2.3、 准备数据并写入到文件
    /**
     * 测试写出数据
     */
    @Test
    public void simpleWrite() {
        // 创造数据
        ArrayList<Student> list = new ArrayList<>( );
        list.add(new Student("张三","男",new Date(),"1001"));
        list.add(new Student("李四","女",new Date(),"1002"));
        list.add(new Student("王五","男",new Date(),"1003"));
        list.add(new Student("赵六","女",new Date(),"1004"));
        list.add(new Student("周期","男",new Date(),"1005"));
        list.add(new Student("茅十八","女",new Date(),"1006"));

        // 写出的文件路径,当前项目下
        String fileName = "student.xlsx";

        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, Student.class).sheet("学生信息").doWrite(list);
        System.out.println("导出OK" );
    }

image-20230618182323185

image-20230618182345134

3、web文件上传和下载[重点]

基于springboot的文件上传和下载

0. 导入依赖

<!-- EasyExcel -->

<!-- lombok -->

<!-- junit -->
3.1 文件上传

需求: 批量插入(导入excel数据到项目中)

思路:

  • 前端设计文件上传组件 ,点击开始文件上传
  • 后端接收文件,使用工具解析数据
  • 插入数据库

3.1.1 前端

在页面设计上传的组件

1.设置上传按钮,显示上传的对话框

image-20231220103151801

image-20231220103251548

2.设置对话框+上传组件

image-20231220103632569

 <!-- 上传excel对话框 -->
    <el-dialog :visible.sync="importExcelVisible" width="30%">
      <el-upload
        class="upload-demo"
        drag
        action="http://localhost:9999/api/sysUser/import"
        name="file"
        method="post"
        accept=".xlsx,.xls"
        :on-success="importSuccessFunction"
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      </el-upload>
    </el-dialog>
3.1.2 后端-实体类注解

image-20231220105240768

3.1.3 后端-监听器
package com.qf.util;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.qf.entity.SysUser;
import com.qf.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

/**
 * --- 天道酬勤 ---
 *
 * @author QiuShiju
 * @desc
 */
@Component
@Scope("prototype")    // 作者要求每次读取都要使用新的Listener
public class SysUserReadListener extends AnalysisEventListener<SysUser> {
    // 有个很重要的点 DemoDataListener 不能被spring管理,
    // 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
    private SysUserService sysUserService;
    public SysUserReadListener(){} // 空参构造

    // 有参构造
    public SysUserReadListener(SysUserService sysUserService){
        this.sysUserService = sysUserService;
    }

    // 每隔5条存储数据库,实际项目中使用时可以100条,然后清理list ,方便内存回收
    private final int BATCH_COUNT = 5;
    private ArrayList<SysUser> list = new ArrayList<>( );

    // 每读一样,会调用该invoke方法一次
    @Override
    public void invoke(SysUser data, AnalysisContext context) {
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size( ) >= BATCH_COUNT) {
            saveData( );
            // 存储完成清理 list
            list.clear();
        }
    }

    // 全部读完之后,会调用该方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData( );
        System.out.println("所有数据解析完成!");
    }

    private void saveData() {
        System.out.println("开始存储数据库!");

        // 调用业务层存数据库
        sysUserService.saveBatch(list);
        System.out.println("存储数据库成功!");
    }
}
3.1.4 后端-Controller接收
    @PostMapping("/import")
    public R importExcel(MultipartFile file) throws IOException {
        // 别忘了给SysUserReadListener构造方法传入SysUserService对象
        EasyExcel.read(file.getInputStream(),
                SysUser.class,
                new SysUserReadListener(sysUserService)).sheet().doRead();
        return R.ok();
    }
3.1.5 后端-Service
// 需要添加批量插入的方法
3.1.6 后端-Mapper
    <insert id="saveBatch">
        insert into sys_user(user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark)
        values
        <foreach collection="list" item="entity" separator=",">
            (#{entity.userName}, #{entity.nickName}, #{entity.userType}, #{entity.email}, #{entity.phonenumber}, #{entity.sex}, #{entity.avatar}, #{entity.password}, #{entity.status}, #{entity.delFlag}, #{entity.loginIp}, #{entity.loginDate}, #{entity.createBy}, #{entity.createTime}, #{entity.updateBy}, #{entity.updateTime}, #{entity.remark})
        </foreach>
    </insert>
3.1.7 测试

本地创建一个excel表格,按照实体类中定义的列名填充数据

点击上传

image-20231220105443796

3.2 文件导出

需求: 将项目中的数据导出到本地excel表格

思路:

  • 前端设计按钮,点击开始导出
  • 后端请求,查询出数据,
  • 使用工具导出

实体类于之前一样

// 略

Controller层

 /**
     * 导出excel
     */
    @GetMapping("/export")
    public void exportExcel(HttpServletResponse response) throws IOException {
        // 调用业务层,查询全部数据
        List<SysUser> list = sysUserService.queryAll();
        // 下面这个注释是前端使用方式1,即a标签发请求时采用
        // response.setContentType("application/vnd.ms-excel");
        // response.setCharacterEncoding("utf-8");
        // // 这里URLEncoder.encode可以防止中文乱码
        // String fileName = URLEncoder.encode("系统用户信息", "UTF-8");
        // response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");



        // 这里需要设置不关闭流
        EasyExcel.write(response.getOutputStream(), SysUser.class).autoCloseStream(Boolean.FALSE).sheet("系统用户信息")
                .doWrite(list);
    }

// todo servicce+mapper实现查询全部

前端

1.按钮发请求导出

image-20231220105831397

image-20231220110201238

2调用自己的封装的函数

image-20231220110859406

   // 导出excel
    exportUserExcel(){
      this.$confirm('确定要导出数据吗?', '信息', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 方案1: 原生js方式发起导出excel请求,后端设置响应格式
        // window.location.href = "http://localhost:8888/api/sysUser/export"
        // 方案1的话后端需要设置响应格式和内容

        // 方案2: 使用axios默认是不行的,需要额外配置
        // 方案2的话后端就不需要设置响应,直接导出就行
        axios.request({
          url: '/api/sysUser/export',
          method: 'get',
          responseType: 'blob',
        }).then(ret=>{
          this.download(ret,"系统用户信息")
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消导出数据!'
        })
      })
    },
    // 下载excel的工具方法
    download(res, name) {
      if (!res) {
        return;
      }
      console.log("res",res)
      let url = window.URL.createObjectURL(new Blob([res.data],
        { type: "application/vnd.ms-excel" }));
      let link = document.createElement('a');
      link.style.display = 'none';
      link.href = url;
      link.setAttribute('download', name);
      document.body.appendChild(link);
      link.click();
    },

4、自定义单元格样式

EasyExcel支持调整行高、列宽、背景色、字体大小等内容,但是控制方式与使用原生POI无异,比较繁琐,不建议使用。

但是可以使用模板填充的方式,向预设样式的表格中直接写入数据,写入数据的时候会保持原有样式。

三、填充-自学

1、填充一组数据

1.1 准备模板

​ Excel表格中用{} 来表示包裹要填充的变量,如果单元格文本中本来就有{}左右大括号,需要在括号前面使用斜杠转义\{\}

​ 代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

1.2 封装数据

编写封装填充数据的类或选用Map

/**
 * 使用实体类封装填充数据
 *
 *  实体中成员变量名称需要和Excel表各种{}包裹的变量名匹配
 */
@Data
public class FillData {

    private String name;
    private int age;
}
/**
 * 生成多组数据代码
 * / 
    private static List<FillData> initFillData() {
        ArrayList<FillData> fillDatas = new ArrayList<FillData>();
        for (int i = 0; i < 10; i++) {
            FillData fillData = new FillData();
            fillData.setName("学生0" + i);
            fillData.setAge(10 + i);
            fillDatas.add(fillData);
        }
        return fillDatas;
    }
1.3 填充

准备数据并填充到文件

public static void main(String[] args) {
    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template1" +
            ".xlsx");

    // 写入文件
    String targetFileName = "单组数据填充.xlsx";

    // 准备对象数据填充
    FillData fillData = new FillData();
    fillData.setName("小熊");
    fillData.setAge(10);


    // 生成工作簿对象
    ExcelWriterBuilder workBookWriter = EasyExcel.write(targetFileName).withTemplate(templateFile);

    // 获取工作表并填充
    //workBookWriter.sheet().doFill(fillData);

    // 使用Map数据填充
    HashMap<String, String> mapFillData = new HashMap<>();
    mapFillData.put("name", "小虎");
    mapFillData.put("age", "11");

    // 获取第一个工作表填充并自动关闭流
    workBookWriter.sheet().doFill(mapFillData);
}

1.4 效果

2、填充多组数据

2.1 准备模板

​ Excel表格中用{.} 来表示包裹要填充的变量,如果单元格文本中本来就有{}左右大括号,需要在括号前面使用斜杠转义\{\}

​ 代码中被填充数据的实体对象的成员变量名或被填充map集合的key需要和Excel中被{}包裹的变量名称一致。

2.2 封装数据

编写封装填充数据的类或选用Map

// 同上
2.3 填充

准备数据并填充到文件

public static void main(String[] args) {
    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template2.xlsx");

    // 写入文件
    String targetFileName = "多组数据填充.xlsx";

    List<FillData> fillDatas = initData();

    // 生成工作簿对象
    ExcelWriterBuilder workBookWriter =
            EasyExcel.write(targetFileName).withTemplate(templateFile);

    // 获取第一个工作表填充并自动关闭流
    workBookWriter.sheet().doFill(fillDatas);
}

3、组合填充

3.1 准备模板

即有多组数据填充,又有单一数据填充,为了避免两者数据出现冲突覆盖的情况,在多组填充时需要通过FillConfig对象设置换行。

1584207785924

3.2 封装数据

编写封装填充数据的类或选用Map

// 同上
3.3 填充

准备数据并填充到文件

public static void main(String[] args) {

    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template3.xlsx");

    // 目标文件
    String targetFileName = "组合数据填充.xlsx";

    List<FillData> fillDatas = initData();

    // 生成工作簿对象
    ExcelWriter excelWriter = EasyExcel.write(targetFileName).withTemplate(templateFile).build();

    // 生成工作表对象
    WriteSheet writeSheet = EasyExcel.writerSheet().build();

    // 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
    FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();
    // 填充查看生成Excel出问题
	 excelWriter.fill(fillDatas, writeSheet); //查看生成Excel出问题
    // 填充并换行
    //excelWriter.fill(fillDatas, fillConfig, writeSheet);

    HashMap<String, String> otherData = new HashMap<>();
    otherData.put("date", "2022-03-14");
    otherData.put("total", "100");
    excelWriter.fill(otherData, writeSheet);

    // 一定关闭
    excelWriter.finish();
}

4、水平填充

4.1 准备模板

水平填充和多组填充模板一样,不一样的地方在于,填充时需要通过FillConfig对象设置水平填充。

4.2 封装数据

编写封装填充数据的类或选用Map

// 同上
4.3 填充

准备数据并填充到文件

public static void main(String[] args) {

    // 加载模板
    InputStream templateFile = FillData.class.getClassLoader().getResourceAsStream(
            "fill_data_template4.xlsx");

    // 写入文件
    String targetFileName = "easyExcelDemo\\水平数据填充.xlsx";

    List<FillData> fillDatas = initData();

    // 生成工作簿对象
    ExcelWriter excelWriter = EasyExcel.write(targetFileName).withTemplate(templateFile).build();

    // 生成工作表对象
    WriteSheet writeSheet = EasyExcel.writerSheet().build();


    // 组合填充时,因为多组填充的数据量不确定,需要在多组填充完之后另起一行
    FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();

    // 填充
    excelWriter.fill(fillDatas, fillConfig, writeSheet);

    HashMap<String, String> otherData = new HashMap<>();
    otherData.put("date", "2022-03-14");
    otherData.put("total", "100");
    excelWriter.fill(otherData, writeSheet);

    // 关闭
    excelWriter.finish();
}

5、 注意事项

​ 为了节省内存,所以没有采用把整个文档在内存中组织好之后再整体写入到文件的做法,而是采用的是一行一行写入的方式,不能实现删除和移动行,也不支持备注写入。多组数据写入的时候,如果需要新增行,只能在最后一行增加,不能在中间位置添加。

6、填充综合练习

report_template.xlsx

/**
 * reprot综合练习
 */
@Test
public void test06() {

    InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream(
            "report_template.xlsx");

    // 目标文件
    String targetFile = "模板写入6-report.xlsx";

    // 写入workbook对象
    ExcelWriter workBook =
            EasyExcel.write(targetFile, FillData.class).withTemplate(templateInputStream).build();

    WriteSheet sheet = EasyExcel.writerSheet().build();

    // 填充配置,开启组合填充换行
    //FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();

    // ****** 准备数据 *******
    // 日期
    HashMap<String, String> dateMap = new HashMap<String, String>();
    dateMap.put("date", "2020-03-16");

    // 总会员数
    HashMap<String, String> totalCountMap = new HashMap<String, String>();
    dateMap.put("totalCount", "1000");

    // 新增员数
    HashMap<String, String> increaseCountMap = new HashMap<String, String>();
    dateMap.put("increaseCount", "100");

    // 本周新增会员数
    HashMap<String, String> increaseCountWeekMap = new HashMap<String, String>();
    dateMap.put("increaseCountWeek", "50");

    // 本月新增会员数
    HashMap<String, String> increaseCountMonthMap = new HashMap<String, String>();
    dateMap.put("increaseCountMonth", "100");


    // 新增会员数据
    List<Student> students = initData();
    // **** 准备数据结束****

    // 写入统计数据
    workBook.fill(dateMap, sheet);
    workBook.fill(totalCountMap, sheet);
    workBook.fill(increaseCountMap, sheet);
    workBook.fill(increaseCountWeekMap, sheet);
    workBook.fill(increaseCountMonthMap, sheet);
    // 写入新增会员
    workBook.fill(students, sheet);
    workBook.finish();

}

四、常用API及注解-了解

1、常用类

  • EasyExcel 入口类,用于构建开始各种操作;
  • ExcelReaderBuilder 构建出一个ReadWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
  • ExcelWriterBuilder 构建出一个WriteWorkbook对象,即一个工作簿对象,对应的是一个Excel文件;
  • ExcelReaderSheetBuilder 构建出一个ReadSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
  • ExcelWriterSheetBuilder 构建出一WriteSheet对象,即一个工作表的对象,对应的Excel中的每个sheet,一个工作簿可以有多个工作表;
  • ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据,我们可以把调用service的代码可以写在其invoke方法内部;
  • WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据,对使用者透明不可见;
  • 所有配置都是继承的 Workbook的配置会被Sheet继承。所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet。

2、读取时的注解

@ExcelProperty

使用位置:标准作用在成员变量上

可选属性:

属性名含义说明
index对应Excel表中的列数默认-1,建议指定时从0开始
value对应Excel表中的列头
converter成员变量转换器自定义转换器需要实Converter接口

使用效果:index属性可以指定当前字段对应excel中的哪一列,可以根据列名value去匹配,也可以不写。

如果不使用@ExcelProperty注解,成员变量从上到下的顺序,对应表格中从左到右的顺序;

**使用建议:**要么全部不写,要么全部用index,要么全部用名字去匹配,尽量不要三个混着用。

代码演示:

// 1. 修改成员变量顺序读取Excel表格
// 2. 修改index属性值读取Excel表格
// 3. 修改value属性值读取Excel表格
@ExcelIgnore

标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

代码演示:

// 4. 忽略id成员变量值读取Excel表格
@DateTimeFormat

标注在成员变量上,日期转换,代码中用String类型的成员变量去接收excel中日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat

// 5. 按照指定的格式写入Excel内容
@NumberFormat

标注在成员变量上,数字转换,代码中用String类型的成员变量去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat

@ExcelIgnoreUnannotated

标注在类上。

不标注该注解时,默认类中所有成员变量都会参与读写,无论是否在成员变量上加了@ExcelProperty 的注解。

标注该注解后,类中的成员变量如果没有标注@ExcelProperty 注解将不会参与读写。

3、 读取时通用参数

ReadWorkbook,ReadSheet 都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。

  • readListener 监听器,在读取数据的过程中会不断的调用监听器。

  • headRowNumber 指定需要读表格的 列头行数。默认有一行头,也就是认为第二行开始起为数据。

  • headclazz二选一。读取文件头对应的列表,会根据列表匹配数据。建议使用clas,就是文件中每一行数据对应的代码中的实体类型。

  • clazzhead二选一。读取文件的头对应的class,也可以使用注解。如果两个都不指定,则会读取全部数据。

  • autoTrim 字符串、表头等数据自动trim

  • password 读的时候是否需要使用密码

4、ReadWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,读取时会自动判断,无需设置。
  • inputStreamfile二选一。建议使用file。
  • fileinputStream二选一。读取文件的文件。
  • autoCloseStream 自动关闭流。
  • readCache 默认小于5M用 内存,超过5M会使用 EhCache,不建议使用这个参数。
  • useDefaultListener @since 2.1.4 默认会加入ModelBuildEventListener 来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到class对象,请调用readListener方法,加入自定义的beforeListenerModelBuildEventListener、 自定义的afterListener即可。

5、ReadSheet(工作表对象)参数

  • sheetNo 需要读取Sheet的编号,建议使用这个来指定读取哪个Sheet
  • sheetName 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配

6、写入时的注解注解

@ExcelProperty

使用位置:标准作用在成员变量上

可选属性:

属性名含义说明
index对应Excel表中的列数默认-1,指定时建议从0开始
value对应Excel表中的列头
converter成员变量转换器自定义转换器需要实Converter接口

使用效果index 指定写到第几列,如果不指定则根据成员变量位置排序;

value指定写入的列头,如果不指定则使用成员变量的名字作为列头;

​ 如果要设置复杂的头,可以为value指定多个值。

代码演示:

    // 5. 为《学员表.xlsx》文件中学生信息设置一个统一的表头“学员信息表”
其他注解:

基本和读取时一致

  • @ContentRowHeight() 标注在类上或属性上,指定内容行高

  • @HeadRowHeight() 标注在类上或属性上,指定列头行高

  • @ColumnWidth() 标注在类上或属性上,指定列宽

  • ExcelIgnore` 默认所有字段都会写入excel,这个注解会忽略这个字段

  • DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat

    是com.alibaba.excel.annotation.format.DateTimeFormat;包下的

  • NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat

  • ExcelIgnoreUnannotated 默认不加 ExcelProperty 的注解的都会参与读写,加了不会参与

7、写入时通用参数

WriteWorkbookWriteSheet都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。

  • writeHandler 写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。

  • relativeHeadRowIndex 距离多少行后开始。也就是开头空几行

  • needHead 是否导出头

  • headclazz二选一。写入文件的头列表,建议使用class。

  • clazzhead二选一。写入文件的头对应的class,也可以使用注解。

  • autoTrim 字符串、表头等数据自动trim

8、WriteWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,默认为xlsx

  • outputStreamfile二选一。写入文件的流

  • fileoutputStream二选一。写入的文件

  • templateInputStream 模板的文件流

  • templateFile 模板文件

  • autoCloseStream 自动关闭流。

  • password 写的时候是否需要使用密码

  • useDefaultStyle 写的时候是否是使用默认头

9、WriteSheet(工作表对象)参数

  • sheetNo 需要写入的编号。默认0

  • sheetName 需要些的Sheet名称,默认同sheetNo

  • DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat

    是com.alibaba.excel.annotation.format.DateTimeFormat;包下的

  • NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat

  • ExcelIgnoreUnannotated 默认不加 ExcelProperty 的注解的都会参与读写,加了不会参与

7、写入时通用参数

WriteWorkbookWriteSheet都会有的参数,如果为空,默认使用上级。

  • converter 转换器,默认加载了很多转换器。也可以自定义。

  • writeHandler 写的处理器。可以实现WorkbookWriteHandler,SheetWriteHandler,RowWriteHandler,CellWriteHandler,在写入excel的不同阶段会调用,对使用者透明不可见。

  • relativeHeadRowIndex 距离多少行后开始。也就是开头空几行

  • needHead 是否导出头

  • headclazz二选一。写入文件的头列表,建议使用class。

  • clazzhead二选一。写入文件的头对应的class,也可以使用注解。

  • autoTrim 字符串、表头等数据自动trim

8、WriteWorkbook(工作簿对象)参数

  • excelType 当前excel的类型,默认为xlsx

  • outputStreamfile二选一。写入文件的流

  • fileoutputStream二选一。写入的文件

  • templateInputStream 模板的文件流

  • templateFile 模板文件

  • autoCloseStream 自动关闭流。

  • password 写的时候是否需要使用密码

  • useDefaultStyle 写的时候是否是使用默认头

9、WriteSheet(工作表对象)参数

  • sheetNo 需要写入的编号。默认0

  • sheetName 需要些的Sheet名称,默认同sheetNo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1324144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构:图解手撕B-树以及B树的优化和索引

文章目录 为什么需要引入B-树&#xff1f;B树是什么&#xff1f;B树的插入分析B树和B*树B树B*树分裂原理 B树的应用 本篇总结的内容是B-树 为什么需要引入B-树&#xff1f; 回忆一下前面的搜索结构&#xff0c;有哈希&#xff0c;红黑树&#xff0c;二分…等很多的搜索结构&a…

超结MOS/低压MOS在5G基站电源上的应用-REASUNOS瑞森半导体

一、前言 5G基站是5G网络的核心设备&#xff0c;实现有线通信网络与无线终端之间的无线信号传输&#xff0c;5G基站主要分为宏基站和小基站。5G基站由于通信设备功耗大&#xff0c;采用由电源插座、交直流配电、防雷器、整流模块和监控模块组成的电气柜。所以顾名思义&#xf…

谈思生物医疗直播|“靶向双硫死亡在肿瘤治疗中的应用”

细胞死亡是维持生物发育和内部环境稳态的生理过程。靶向细胞死亡相关通路杀死癌细胞是癌症治疗的一大方向。今年年初&#xff0c;有研究团队发现和鉴定了一种全新的细胞死亡类型——双硫死亡(Disulfidptosis)&#xff0c;为癌治疗开辟了新的可能性。 溶质载体家族成员 SLC7A11…

Linux网络编程(二):Socket 编程

参考引用 黑马程序员-Linux 网络编程 1. 套接字概念 Socket 本身有 “插座” 的意思&#xff0c;在 Linux 环境下&#xff0c;用于表示进程间网络通信的特殊文件类型 本质为内核借助缓冲区形成的伪文件 既然是文件&#xff0c;那么可以使用文件描述符引用套接字 与管道类似&am…

CGAL中流线的二维放置

本章介绍CGAL 2D流线放置包。定义一节给出了基本定义和概念。基本概念一节对整合过程进行了描述。最远点播种策略一节简要介绍了该算法。“实现”一节介绍了包的实现&#xff0c;“示例”一节详细介绍了两个示例放置。 该算法的核心思想是对域中最大空腔中心的流线进行积分&am…

HuggingFace下载模型

目录 方式一&#xff1a;网页下载 方式二&#xff1a;Git下载 方式一&#xff1a;网页下载 方式二&#xff1a;Git下载 有些模型的使用方法页面会写git clone的地址&#xff0c;有些没写&#xff0c;直接复制网页地址即可 网页地址&#xff1a; ​https://huggingface.co/…

12.19_黑马数据结构与算法笔记Java

目录 203 排序算法 选择排序 204 排序算法 堆排序 205 排序算法 插入排序 206 排序算法 希尔排序 207 排序算法 归并排序 自顶至下 208 排序算法 归并排序 自下至上 209 排序算法 归并加插入 210 排序算法 单边快排 211 排序算法 双边快排 212 排序算法 快排 随机基准…

技术博客:市面上加密混淆软件的比较和推荐

引言 市面上有许多加密混淆软件可供开发者使用&#xff0c;但哪些软件是最好用的&#xff1f;哪些软件受到开发者的喜爱&#xff1f;本文将根据一次在CSDN上的投票结果&#xff0c;为大家介绍几款在程序员中普及度较高的加密软件。以下是投票结果&#xff0c;希望能对大家的选…

【jvm从入门到实战】(十) 实战篇-内存调优

内存溢出和内存泄漏&#xff1a;在Java中如果不再使用一个对象&#xff0c;但是该对象依然在GC ROOT的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情况就称之为内存泄漏。内存泄漏绝大多数情况都是由堆内存泄漏引起的。少量的内存泄漏可以容忍&#x…

MySQL5.x与8.0

大致区别 1. 性能&#xff1a;MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍 MySQL 8.0 在以下方面带来了更好的性能&#xff1a;读/写工作负载、IO 密集型工作负载、以及高竞争&#xff08;"hot spot"热点竞争问题&#xff09;工作负载2. NoSQL&#xff1a;MySQL 从 5.7 …

CPU算力分配 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 现有两组服务器A和B,每组有多个算力不同的CPU,其中 A 是A组第个CPU的运算能力,是 B组 第个CPU的运算能力。一组服务器的总算力是各CPU的算力之和。 为了让两组服务器的算力相等,允许从每组各选出一个CPU进行一次交换。 求…

基于PHP的蛋糕购物商城系统

有需要请加文章底部Q哦 可远程调试 基于PHP的蛋糕购物商城系统 一 介绍 此蛋糕购物商城基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈&#xff1a;phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销…

做一个wiki页面是体验HTML语义的好方法

HTML语义&#xff1a;如何运用语义类标签来呈现Wiki网页 在上一篇文章中&#xff0c;我花了大量的篇幅和你解释了正确使用语义类标签的好处和一些场景。那么&#xff0c;哪些场景适合用到语义类标签呢&#xff0c;又如何运用语义类标签呢&#xff1f; 不知道你还记不记得在大…

爱芯派pro通过无线网卡rtl8188eu连接热点

爱芯派pro通过无线网卡rtl8188eu连接热点 爱芯派pro目前的底板的pcie的复位有问题&#xff0c;所以pcie接口无法挂载上去&#xff0c;所以自己购买的rtl8822网卡也用不了&#xff0c;然后想起来自己还有正点原子的rtl8188eu网卡&#xff0c;但是没有和工作人员进行摸索后才知道…

Swagger升级指南:Swagger2与Swagger3注解差异揭秘

在API开发的世界里&#xff0c;Swagger已经成为了一个不可或缺的工具&#xff0c;它让API的文档化和前后端的协作变得前所未有地简单。随着Swagger的进化&#xff0c;我们迎来了Swagger3&#xff0c;也被称为OpenAPI Specification 3.0。本篇博客将带大家深入了解Swagger2和Swa…

【Python 基础】-- 在 mac OS 中安装 多个 python 版本

目录 1、需求 2、实现 2.1 安装 pyenv 2.2 安装 pyenv-virtualenv 2.3 配置环境变量 2.4 创建 python 3.9.9 的环境 2.5 激活环境&#xff0c;在当前项目目录中使用&#xff0c;即执行 python 1、需求 由于项目所依赖的 python 版本有多个&#xff0c;需要在不同的 pyth…

在线客服系统中的全渠道服务:多渠道整合与无缝沟通体验

很多采购人员在了解在线客服系统的时候都会遇到一个名词——全渠道。很多人第一次接触可能并不理解它是什么意思&#xff0c;也不知道自己的企业是否需要这个”全渠道“。今天这篇文章就为大家解答一二。 一、全渠道是什么&#xff1f; 全渠道 (Omni-Channel)&#xff0c;就是…

DeepLabV3+实现sar影像海面溢油区识别

今天我们分享DeepLabV3的sai影像水体提取。 数据集 本次使用的数据集是Deep-SAR Oil Spill (SOS) dataset。该数据集由中国地质大学的朱祺琪团队制作并共享。该数据集包含墨西哥湾溢油区域和波斯湾溢油区域&#xff0c;分别获取于ALOS 和Sentinel-1A卫星。由ECHO研究组搜集制作…

说说 Spring Boot 实现接口幂等性有哪几种方案?

一、什么是幂等性 幂等是一个数学与计算机学概念&#xff0c;在数学中某一元运算为幂等时&#xff0c;其作用在任一元素两次后会和其作用一次的结果相同。 在计算机中编程中&#xff0c;一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等…

【go语言】error错误机制及自定义错误返回类型

简介 Go 语言通过内置的 error 接口来处理错误&#xff0c;该接口定义如下&#xff1a; type error interface {Error() string }这意味着任何实现了 Error() 方法的类型都可以作为错误类型。在 Go 中&#xff0c;通常使用 errors 包的 New 函数来创建简单的错误&#xff1a;…