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

文章目录:

1.EasyExcel

2.Excel的上传(读Excel)

3.Excel的下载(写Excel)

4.结语


1.EasyExcel

Hello,大家好啊,好久不见了,自工作之后真的很难腾出时间来写博客(给大家分享我在技术中遇到的那些事),在华为真的挺忙的,这不突然来了一个双休,我也不想闲着,回想起很早之前学过的一个东西SpringBoot以及EasyExcel,于是就想着把它俩整合一下搞点简单的功能玩玩。


首先,这里给出EasyExcel的官方文档:https://easyexcel.opensource.alibaba.com/

alibaba.com不用我多说了吧,大家都认识,这个东西就是阿里开发的:EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
(这段话听着好牛的样子,没错,官方给出的你说呢)

那么由于下面的案例中,涉及到Excel表格的样式问题,所以我这里采用了 EasyExcel 3.1.x的版本。https://easyexcel.opensource.alibaba.com/docs/current/

开始吧,

2.Excel的上传(读Excel)

我这里首先给大家分享一下SpringBoot结合EasyExcel实现对 Excel 中数据的读取,并且将读取到的数据存入MySQL数据库。

说到这里,要加什么依赖大家肯定都心知肚明了。

        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.1.4
        

        
            mysql
            mysql-connector-java
            5.1.9
        

        
            org.projectlombok
            lombok
            true
        

        
            com.alibaba
            easyexcel
            3.1.1
        

        
            com.alibaba
            fastjson
            1.2.28
        

由于用到MyBatis,所以一定不要忘记加下面的这段代码,否则你的mapper是编译不到你的classpath中的。

        
            
                src/main/java
                
                    **/*.xml
                
            
        

既然要读取Excel,同时存入数据库,那么就必然需要对应的表了,这就引出了  表对应的实体类。

我这里写了两个,这是因为 Excel 对应了一个类,而数据库表对应的是另外一个,原因考虑到可能Excel表格会增加一些不必要的字段,而这些字段并不需要存入数据库中(反过来也是一样,可能数据库实体类中有10个字段,而Excel这边仅有6个字段,而剩余的4个字段我们可能会从其他渠道获取)。

上面我说的可能是在公司实际开发中会遇到的某些场景吧,但是为了方便大家理解,这两个类我还是定义的相同的字段。

package com.szh.dto;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodsExcelDTO {

    @ExcelProperty(index = 0)
    private Integer id;

    @ExcelProperty(index = 1)
    private String name;

    @ExcelProperty(index = 2)
    private String info;

    @ExcelProperty(index = 3)
    @DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")
    private String buyTime;
}
package com.szh.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

/**
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {

    private Integer id;

    private String name;

    private String info;

    //@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date buyTime;
}

大家应该能发现,Excel对应的类中 buyTime是 String类型,而数据库实体类的 buyTime是Date类型,这是因为: (阿里官方文档给出的规定)

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第1张图片

下面给出goods这张表的sql脚本。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for goods
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品名称',
  `info` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品信息',
  `buyTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '购买时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, 'Java核心技术卷I', 'Java四大名著之一', '2021-12-01 13:05:23');
INSERT INTO `goods` VALUES (2, 'Java核心技术卷II', 'Java四大名著之一', '2022-02-28 09:21:55');
INSERT INTO `goods` VALUES (3, 'Java编程思想', 'Java四大名著之一', '2020-05-15 14:16:18');
INSERT INTO `goods` VALUES (4, 'Effective Java', 'Java四大名著之一', '2022-07-06 22:33:44');
INSERT INTO `goods` VALUES (5, '深入理解Java虚拟机', 'JVM由此开始', '2021-06-06 20:59:37');
INSERT INTO `goods` VALUES (6, 'SpringBoot实战应用教程', 'SpringBoot极速开发框架', '2020-03-17 10:02:31');
INSERT INTO `goods` VALUES (7, '快速上手SSM', '一本优秀的书籍', '2021-08-14 19:08:26');
INSERT INTO `goods` VALUES (8, 'MySQL是怎样运行的', '深入学习MySQL', '2022-04-09 15:37:05');
INSERT INTO `goods` VALUES (9, 'Java并发编程', '并发编程之美', '2020-01-18 21:12:13');
INSERT INTO `goods` VALUES (10, 'Java从入门到精通', '小白的Java之路', '2021-09-11 17:18:21');

SET FOREIGN_KEY_CHECKS = 1;

大家参考官方文档之后,肯定知道,针对Excel的读,我们需要写一个监听器,去不断的监听读取Excel的每一行数据,最终加入数据库类似这样的操作。

下面就是监听器代码。

有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去。这也就是监听器中需要声明 

private GoodsService goodsService; 这个成员变量的原因。

invoke 方法:解析Excel中的每一条数据都会来调用它。

invokeHead 方法:针对Excel表头的一些操作。

doAfterAllAnalysed 方法:所有数据解析完成了会来调用它。

package com.szh.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.szh.dto.GoodsExcelDTO;
import com.szh.model.Goods;
import com.szh.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Excel模板的读取类
 * 有个很重要的点 DemoDataListener 不能被spring管理
 * 要每次读取excel都要new, 然后里面用到spring可以构造方法传进去
 */
@Slf4j
public class GoodsListener implements ReadListener {

    private GoodsService goodsService;
    private List goodsExcelDTOList = ListUtils.newArrayList();
    private final List goodsList = ListUtils.newArrayList();
    private int count = 0;

    public GoodsListener() {
    }

    public GoodsListener(GoodsService goodsService) {
        this.goodsService = goodsService;
    }

    @Override
    public void invoke(GoodsExcelDTO goodsExcelDTO, AnalysisContext analysisContext) {
        log.info("解析到第 {} 条数据:{}", (++count), JSON.toJSONString(goodsExcelDTO));
        goodsExcelDTOList.add(goodsExcelDTO);
    }

    @Override
    public void invokeHead(Map> headMap, AnalysisContext context) {
        Map stringMap = ConverterUtils.convertToStringMap(headMap, context);
        Set keySet = stringMap.keySet();
        System.out.println("该Excel表头信息是:");
        for (int i = 0; i < keySet.size(); i++) {
            System.out.println("第 " + (i + 1) + " 列 = " + stringMap.get(i));
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("该Excel的所有数据解析完成!!!");
        goodsExcelDTOList.forEach(item -> {
            try {
                Goods goods = new Goods();
                goods.setId(item.getId());
                goods.setName(item.getName());
                goods.setInfo(item.getInfo());
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = sdf.parse(item.getBuyTime());
                goods.setBuyTime(date);
                goodsList.add(goods);
            } catch (ParseException e) {
                log.error(e.getMessage());
            }
        });
        goodsExcelDTOList = ListUtils.newArrayList();
        goodsService.batchInsert(goodsList);
    }
}

好的,下面就是我们的mapper、service、controller了。这都是大家很熟悉的东西了。

package com.szh.mapper;

import com.szh.dto.GoodsExcelDTO;
import com.szh.model.Goods;

import java.util.List;

/**
 *
 */
public interface GoodsMapper {

    /**
     * 批量插入
     */
    void batchInsert(List goodsList);
}



    

    
        insert into goods
        values
        
            (#{item.id}, #{item.name}, #{item.info}, #{item.buyTime})
        
    
package com.szh.service;

import com.szh.model.Goods;

import java.util.List;

/**
 *
 */
public interface GoodsService {

    void batchInsert(List goodsList);
}
package com.szh.service.impl;

import com.szh.mapper.GoodsMapper;
import com.szh.model.Goods;
import com.szh.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 *
 */
@Slf4j
@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsMapper goodsMapper;

    @Transactional
    @Override
    public void batchInsert(List goodsList) {
        goodsMapper.batchInsert(goodsList);
    }
}
package com.szh.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.szh.dto.GoodsExcelDTO;
import com.szh.dto.StudentExcelDTO;
import com.szh.listener.GoodsListener;
import com.szh.service.GoodsService;
import com.szh.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

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

/**
 *
 */
@RestController
@RequestMapping(value = "/excel")
public class EasyExcelController {

    @Autowired
    private GoodsService goodsService;

    /**
     * Excel文件上传
     */
    @PostMapping(value = "/upload")
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), GoodsExcelDTO.class, new GoodsListener(goodsService)).sheet().headRowNumber(1).doRead();
        return "success";
    }

}

再然后就是SpringBoot的核心配置文件和主启动类。

server:
  port: 8001
  servlet:
    context-path: /boot

spring:
  application:
    name: web-easyexcel
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/excel?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
    username: root
    password: 12345678

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
package com.szh;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableTransactionManagement
@MapperScan(basePackages = "com.szh.mapper")
public class WebEasyExcelApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebEasyExcelApplication.class, args);
    }

}

由于这是上传操作,所以我这里也给大家提供我创建的Excel文档。可以看到和上面的goods表的字段都是对应好的。

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第2张图片

下面我们启动项目,在postman中对上传Excel接口进行测试。

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第3张图片

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第4张图片

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第5张图片

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第6张图片

从上面的IDEA中的控制台可以看到,Excel的解析没有问题,顺利执行完毕,同时10条数据也成功写入数据库中。 


3.Excel的下载(写Excel)

这里仍然用到的和上面的读Excel是同一个SpringBoot项目,所以依赖、核心配置文件、主启动类这些都是一样的,就不再给出了,大家参考上面的就行了。

那么对于Excel的下载,也就是说向Excel中写入数据,相较于上传操作就要简单多了。

来看下面的步骤:

这里模拟的场景是:从数据库中的某张表获取到对应的数据集,然后将这个List集合写入Excel中。

所以自然也是需要实体类,那么和上面的上传操作一样,我仍然是创建两个类,一个是Excel表格对应的实体类,另一个是我们数据库对应的实体类。

package com.szh.dto;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 类上面的注解,来自lombok和EasyExcel官方,用来自定义表头、宽度等信息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ColumnWidth(25)
@HeadRowHeight(30)
@ContentRowHeight(20)
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)
public class StudentExcelDTO {

    @ExcelProperty({"学生综述", "学生编号"})
    private Integer id;

    @ExcelProperty({"学生综述", "学生姓名"})
    private String name;

    @ExcelProperty({"学生综述", "学生年龄"})
    private Integer age;

    @ExcelProperty({"学生综述", "学生爱好"})
    private String hobby;

    @ExcelIgnore
    private String address;
}
package com.szh.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {

    private Integer id;

    private String name;

    private Integer age;

    private String hobby;

    private String address;
}

student这张表的sql脚本,我也给到大家。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,暂定学生编号',
  `name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '学生姓名',
  `age` int(11) NOT NULL COMMENT '学生年龄',
  `hobby` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '学生爱好',
  `address` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '学生住址',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '张三', 41, '睡觉', '湖北省武汉市');
INSERT INTO `student` VALUES (2, '李四', 66, '吃饭', '湖南省长沙市');
INSERT INTO `student` VALUES (3, '王五', 28, '打游戏', '江苏省南京市');
INSERT INTO `student` VALUES (4, '赵六', 35, '玩手机', '浙江省杭州市');
INSERT INTO `student` VALUES (5, '田七', 52, '旅游', '上海市黄浦区');
INSERT INTO `student` VALUES (6, '小哥', 22, '盗墓', '河南省洛阳市');
INSERT INTO `student` VALUES (7, '张起灵', 18, '倒斗', '北京市海淀区');
INSERT INTO `student` VALUES (8, '闷油瓶', 20, '挖坟', '广东省深圳市');

SET FOREIGN_KEY_CHECKS = 1;

下面就是我们的mapper、service、controller了,还是大家熟悉的那一套。

package com.szh.mapper;

import com.szh.model.Student;

import java.util.List;

/**
 *
 */
public interface StudentMapper {

    /**
     * 获取学生列表
     */
    List queryAllStudents();
}



    

    
        id, name, age, hobby, address
    

    
        
        
        
        
        
    

    

package com.szh.service;

import com.szh.dto.StudentExcelDTO;

import java.util.List;

/**
 *
 */
public interface StudentService {

    List queryAllStudents();
}
package com.szh.service.impl;

import com.szh.dto.StudentExcelDTO;
import com.szh.mapper.StudentMapper;
import com.szh.model.Student;
import com.szh.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 *
 */
@Slf4j
@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Override
    public List queryAllStudents() {
        List studentList = studentMapper.queryAllStudents();
        List students = new ArrayList<>();
        studentList.forEach(item -> {
            StudentExcelDTO student = new StudentExcelDTO();
            BeanUtils.copyProperties(item, student);
            students.add(student);
        });
        return students;
    }
}

这里的controller和上面的Excel上传是同一个controller,代码可以合并。 

package com.szh.controller;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.szh.dto.GoodsExcelDTO;
import com.szh.dto.StudentExcelDTO;
import com.szh.listener.GoodsListener;
import com.szh.service.GoodsService;
import com.szh.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

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

/**
 *
 */
@RestController
@RequestMapping(value = "/excel")
public class EasyExcelController {

    @Autowired
    private StudentService studentService;

    /**
     * Excel文件下载
     */
    @GetMapping(value = "/download")
    public void download(HttpServletResponse response) throws IOException {
        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");
        List studentList = studentService.queryAllStudents();
        EasyExcel.write(response.getOutputStream(), StudentExcelDTO.class).sheet("sheet1").doWrite(studentList);
    }

    /**
     * Excel文件下载并且失败的时候返回json
     */
    @GetMapping(value = "/downloadFailedUsingJson")
    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        try {
            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");
            List studentList = studentService.queryAllStudents();
            EasyExcel.write(response.getOutputStream(), StudentExcelDTO.class).autoCloseStream(Boolean.FALSE).sheet("sheet1").doWrite(studentList);
        } catch (Exception e) {
            //重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map map = MapUtils.newHashMap();
            map.put("status", "failure");
            map.put("message", "Excel文件下载失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }
}

核心配置文件、主启动类这些都是和上面的案例一样的,就不再给出了。

下面直接启动项目,在postman中进行测试。

这里大家注意一下,有的小伙伴可能直接点了Send,然后postman反馈了一堆乱码,但是此时把接口地址拿到浏览器中是可以直接访问的。

解决办法就是:点击下图中的 Send and Download。

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第7张图片

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第8张图片

保存之后,我们的Excel下载接口就可以正常跑通,下面就是下载成功的Excel了。 

SpringBoot——快速整合EasyExcel实现Excel的上传下载_第9张图片


4.结语

上面的两个案例就是SpringBoot整合EasyExcel,实现Excel的读写(上传下载)的操作。

我这里所有的整合步骤均来自阿里的EasyExcel官方文档,所以大家有什么疑问都可以去官方文档中查找,地址在文章开头已经给出了。

或者大家也可以在文章下面评论,我都会一一回复的,和大家一起讨论技术话题。

你可能感兴趣的:(#,【SpringBoot】,spring,boot,java,easyexcel)