Java implements excel export using easyExcel

Preface

In our project requirements, we often encounter export requirements, among which excel export is the most common. The more famous frameworks for generating Excel include Apache poi, jxl, etc., but they all have a serious problem that they consume a lot of memory. If your system does not have a large amount of concurrency, it may be OK, but once concurrency occurs, it will definitely cause OOM or JVM. Frequent full gc.

1. Features of EasyExcel

EasyExcel is an excel processing framework open sourced by Alibaba. It is famous for its ease of use and memory saving. It can
read 75M (46W rows and 25 columns) of Excel within 1 minute of 64M memory (of course, there is also a quick mode that can be faster, but the memory usage will increase. A little over 100M).
The main reason why EasyExcel can greatly reduce the memory usage is that it does not load all the file data into the memory at one time when parsing Excel, but reads the data line by line from the disk and parses them one by one.

Unsupported features

1. Concurrent writing of a single file,
2. Reading and reading pictures
3. Macro
4. CSV reading (this may be considered in the future)

3. Frequently Asked Questions

1. Be sure to use 2.0.5+ to read files (2.2.10 is currently used in the project)
2. Cglib dynamic proxy is used to read and write reflection objects, so member variables must conform to the camel case specification, and @Accessors cannot be used when using @Data (chain = true). Support for non-humpback will be considered in the future.
3. NoSuchMethodException, ClassNotFoundException, NoClassDefFoundError occurs. The high probability is a jar conflict. It is recommended to clean the project or unify the POI version. Theoretically, easyexcel is compatible with all newer versions of POI 3.17, 4.0.1, 4.1.0.
4. Use String to receive numbers, and decimal points will appear. It is a BUG, ​​but it is difficult to fix. This problem will be fixed in subsequent versions. Currently, please use the @NumberFormat annotation. The parameters inside call the NumberFormat.format method that comes with Java. If you don’t know how to enter the parameters, you can check it online.
The official document address of easyExcel: https://alibaba-easyexcel.github.io/index.html

4. Commonly used annotations

4-1. Read

ExcelProperty specifies the column in excel that the current field corresponds to. You can match based on name or Index. Of course, you don’t have to write it. The default first field is index=0, and so on. Be careful, either don’t write anything, use index, or use names to match. Never mix the three, unless you know very well how to sort the three in the source code.

@Getter
@Setter
@EqualsAndHashCode 
public class IndexOrNameData{
    
    

//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*
@ExcelProperty(index = 2)
private Double doubleData

/*用名字去匹配,这里需要注意,如果名字重复会导致只有一个字殷读取到数据@ExcelProperty ("字符串标题”) */
private Stringstring;

@ExcelProperty ("日期标题") 
private Date date;
}

ExcelIgnore defaults to all fields matching excel. Adding this annotation will ignore the field.

//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*
@ExcelIgnore
private Double doubleData

DateTimeFormat date conversion, using String to receive data in excel date format will call this annotation. The value inside refers to java.text.SimpleDateFormat.

@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String date;

NumberFormat number conversion, using String to receive data in excel number format will call this annotation. The value inside refers to java.text.DecimalFormat.

@NumberFormat("#.##%") 
private String doubleData; //接收百比的数字

4-2. Write

ExcelProperty index specifies which column to write to, and is sorted by member variables by default. value specifies the name to be written, the name of the default member variable. For multiple values, you can refer to the complex header
ExcelIgnore in the quick start. By default, all fields will be written to excel. This annotation will ignore this field.
DateTimeFormat date conversion. Writing Date to excel will call this annotation. The value inside refers to java.text.SimpleDateFormat
NumberFormat number conversion. Writing Excel with Number will call this annotation. The value inside refers to java.text.DecimalFormat
ExcelIgnoreUnannotated. By default, if the ExcelProperty annotation is not added, it will participate in reading and writing. If it is added, it will not participate.

5. Use of EasyExcel

1. Dependence

<dependency>    
<groupId>com.alibaba</groupId>   
 <artifactId>easyexcel</artifactId>    
<version>2.2.10</version>
</dependency>

2. Read excel

Insert image description here

2.1 The simplest

object

@Data
public class DemoData {
    
        
private String string;    
private Date date;    
private Double doubleData;
}

controller class

@PostMapping("/outstoragcExce1")
@Apioperation("读取出库excel表")
public DeviceResponse storageservice(@RequestBody MultipartFile file) {
    
    
	try{
    
    
		storageservice.storageservice(file);
		} catch (Exception e){
    
    
	return new DeviceResponse(Constant.FAIL CODE,"出库导失败");
	}
	return new DeviceResponse(Constant.SUCCESS CODE,"出库导入成功");
}

service implementation class

@Autowired
private StorageService storageServicel
@override
publil void slorageservice(MulliparlFile file) {
    
    
  Tnnutstream is = null:
  try{
    
    
     is=file.getInputstream();
  } catch (IDException e){
    
    
     e.printstackTrace();
  }
  //1.进行读取数数据,slorageReLrieval是我的puju类,
  //2.new Soragelistenpr(storagpServire)这个是监听器,主要用来i取数据的,别急后面会讲
  //3.特别注意的是storageservice这个service,我上面有注入进去 @Autowired,切记不要new会报错
  EasyExcel.read(is,StorageRetrieval.class, new Soragelisterer(storageService))sheet().doRead();
}

SorageListener listener

@Component
public class SorageListener extends AnalysisEventListener<pojo类> {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(SorageListener.class);
    //读取数据初始化值
    private static final int BATCH_COUNT = 50;
    List<pojo类> list = new ArrayList<pojo类>();

    private StorageService storageService;

    public SorageListener() {
    
    
        storageService=new StorageServiceImpl();
    }
 
    public SorageListener(StorageService storageService) {
    
    
        this.storageService = storageService;
    }

    /**
     * 这个每一条数据解析都会来调用,数据是一条一条进行解析的
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(StorageRetrieval data, AnalysisContext context) {
    
    
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
    
    
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
/**
     * 所有excel表中数据解析完成了 都会来调用这个
     * 解释为什么要保存数据?
     *初始化读取数量为50,表中信息已经加载完毕,,假设excel表中最后只剩下30行遗留数据,所以为了防止存在遗留数据 尽量判断下集合是否为空,不为空在进行存储(这是我的逻辑需要判断,如果不需要也可进行不判断)
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    
    
      if(list.size()==0){
    
    
         return;
      }
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    public void saveData() {
    
    
        storageService.save(list); //代码实现类层保存数据
        LOGGER.info("存储数据库成功!");
    }
}

2.2. Specify the subscript or column name of the column

@Data
public class IndexOrNameData {
    
    
    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
}
/**
 * 指定列的下标或者列名
 *
 * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
 * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
 * <p>3. 直接读即可
 */
@Test
public void indexOrNameRead() {
    
    
    String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
    // 这里默认读取第一个sheet
    EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}

3. Write excel

3.1 The simplest

Insert image description here
entity object

@Data
@piModel(value = "年龄统计实体类”
public class FToWAgeStatisticalVo implements Serializable {
    
    
private static final long serialVersionUID = -7891558029837989473L;
@ApiModelProperty("区间")
@ExcelProperty(value = "区间")
private String ageGap;

@ApiModeLProperty("病例数”)
@ExcelProperty(value ="病例数)
private Integer casesNumber ;

@ApiModeProperty("密接数”)
@ExceProperty(value = "密接数”)
private Integer closeNumber ;
}

service implementation

@Override
public void avestatisticalExcel(Httpservlethesponse resonse,FlowReionStatisticalParam flowReionStatisticalParam) throws Exception{
    
    
  //这里文件名如果涉及中文一定要使用URL编码,否则会乱码
  String fileName = URLEncoder.encode( s: "floWAgeStatistical.xlsx" StandardCharsets.UTF_8.toString());
  List<FloWAgeStatisticalVo> data = ageStatistical(flowRegionStatisticalParam);
  response.setContentType("application/force-download");
  response.setcharacterEncoding("utf-8");
  response.setHeader( s: "Content-Disposition", s1: "attachment;filename=" + fileName);
  EasyExcel.write(response.getoutputstream()FLoWAgeStatisticalVo.class)
    .autoclosestream(true)
    .exceType(ExcelTypeEnum.XLSX)
    .sheet( sheetName: "年龄统计表")
    .doWrite(data) ;
}

3.2. Column width and row height

@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
    
    
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 宽度为50
     */
    @ColumnWidth(50)
    @ExcelProperty("数字标题")
    private Double doubleData;
}

3.3. Merge cells

@Getter
@Setter
@EqualsAndHashCode
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
    
    
    // 这一列 每隔2行 合并单元格
    @ContentLoopMerge(eachRow = 2)
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}
/**
  * 合并单元格
  * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
  * <p>2. 创建一个merge策略 并注册
  * <p>3. 直接写即可
  */
 @Test
 public void mergeWrite() {
    
    
     String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
     // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
     LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
     // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
     EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
         .doWrite(data());
 }

3.4. Complex header writing

Insert image description here

@Data
@ApiModel("学校学生缺勤信息")
public class SchoolAnalyseVo {
    
    

    @ApiModelProperty("学校Id")
    @ExcelIgnore()
    private Long schoolId;

    @ExcelProperty("学校")
    private String schoolName;

    @ExcelProperty("学校类型")
    private String schoolType;
     ..........

    @ExcelProperty({
    
    "症状", "发热"})
    private String fever;

    @ExcelProperty({
    
    "症状", "咳嗽"})
    private String cough;

    @ExcelProperty({
    
    "症状", "头痛"})
    private String headache;
    .........

    @ExcelProperty({
    
    "疾病","普通感冒", "人数"})
    private String commonColdNumber ;

    @ExcelProperty({
    
    "疾病","普通感冒", "因病缺勤率"})
    private String commonColdRate ;

    @ExcelProperty({
    
    "疾病","流感", "人数"})
    private String influenzaNumber;
    .........
}

3.5. Date, number or custom format conversion

Insert image description here

@Data
public class ConverterData {
    
    
    /**
     * 我想所有的 字符串起前面加上"自定义:"三个字
     */
    @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
    private String string;
    /**
     * 我想写到excel 用年月日的格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty("日期标题")
    private Date date;
    /**
     * 我想写到excel 用百分比表示
     */
    @NumberFormat("#.##%")
    @ExcelProperty(value = "数字标题")
    private Double doubleData;
}

Custom converter

public class CustomStringStringConverter implements Converter<String> {
    
    
    @Override
    public Class supportJavaTypeKey() {
    
    
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
    
    
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param cellData
     * @param contentProperty
     * @param globalConfiguration
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
    
    
        return "自定义:" + cellData.getStringValue();
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value
     * @param contentProperty
     * @param globalConfiguration
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
    
    
        return new CellData(value);
    }
}

3.6. Specify the write column

Insert image description here

@Getter
@Setter
@EqualsAndHashCode
public class IndexData {
    
    
    @ExcelProperty(value = "字符串标题", index = 0)
    private String string;
    @ExcelProperty(value = "日期标题", index = 1)
    private Date date;
    /**
     * 这里设置3 会导致第二列空的
     */
    @ExcelProperty(value = "数字标题", index = 3)
    private Double doubleData;
}

3.7. Other read operations

https://www.yuque.com/easyexcel/doc/write

4. Fill excel

4.1 The simplest filling

Insert image description here
object

@Getter
@Setter
@EqualsAndHashCode
public class FillData{
    
    
private string name;
private double number;
private Date date;
}

code

/*最简单的填充
* @since 2.1.1
*/
@Test
public void simpleFill() [
    // 模板注 用]来表示你要用的变量 如果本来就有””,”]”特殊字符 用””]"代替
    String templateFileName =TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx";
    // 方案1 根据对象填充
    String fileName = TestFileUtil,getPath() + "simpleFill" + System,currentTimeMillis() + ".xlsx",
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    FillData fillData = new FillData();
    fillData.setName("张一");
    fillData.setNumber(5.2);
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
    // 方案2 根据Map填充
    fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"
    // 这里 会填充到第一个sheet, 然后文件流会自动关闭
    Map<string, Object> map = new HashMap<string, Object>();
    map.put("name”,"张二");
    map .put("number", 5.2) :
    EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}

4.2. Other filling

Filling lists, complex filling, complex filling with large amounts of data, horizontal filling, multi-list combination filling
https://www.yuque.com/easyexcel/doc/fill

Guess you like

Origin blog.csdn.net/weixin_43945397/article/details/129532669