文中涉及的代码,已上传码云:https://gitee.com/yaoXu9956/easy-excel

一、数据准备

第一步:项目搭建
新建maven项目,导入pom依赖,书写启动类;

<?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 http://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.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>easyExcel</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--EasyExcel相关依赖--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.1</version></dependency><!-- 糊涂工具包 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.20</version></dependency></dependencies></project>
package com.ifly.task;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @program: easyExcel* @description:* @create: 2022-07-29 14:21**/
@SpringBootApplication
public class EasyExcelApplication {public static void main(String[] args) {SpringApplication.run(EasyExcelApplication.class, args);}
}

第二步:集成JPA

书写 application.properties

server.port=80# 数据库基本配置
# 只需要创建库,无需创建表
spring.datasource.url=jdbc:mysql://localhost:3306/jpa?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# 表中有数据时不会清空,只会更新
spring.jpa.hibernate.ddl-auto=update# 控制台显示sql
spring.jpa.show-sql=true

第三步:构建实体类,jpa自动创建表

注意这些注解的使用;

@Entity : 表示这是一个ORM实体,根据这个实体创建表;

@Table(name = “user”) :指定表的名称;

@Id:指定某个属性是主键;

@Column(name = “user_name”, columnDefinition = “varchar(128) COMMENT ‘人物名称’”):该注解的name属性表示该注解对应的实体类属性在表中的名称,columnDefinition 是对表的字段做一个限制和说明;

package com.ifly.task.pojo;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "user")
public class User {@Id@Column(nullable = false, columnDefinition = "bigint comment '主键id'")@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "user_name", columnDefinition = "varchar(128) COMMENT '人物名称'")private String userName;/*** 性别* 1:男 2:女 3:保密*/@Column(name = "gender", columnDefinition = "int(4) DEFAULT 1 COMMENT '性别, 1:男 2:女 3:保密'")private Integer gender;/*** 人物所属类型*/@Column(name = "type", columnDefinition = "int DEFAULT 11 COMMENT '人物所属类型'")private Integer type;}
package com.ifly.task.pojo;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;/*** @program: excel* @description: todo* @create: 2022-07-21 23:28**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "country")
public class Country {@Id@Column(nullable = false, columnDefinition = "bigint comment '主键id'")@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "country_name", columnDefinition = "varchar(128) COMMENT '国家名称'")private String countryName;@Column(name = "power", columnDefinition = "varchar(128) COMMENT '掌权者名字'")private String power;@Column(name = "prime_minister", columnDefinition = "varchar(128) COMMENT '丞相名字'")private String primeMinister;@Column(name = "create_time", columnDefinition = "datetime COMMENT '国家建立时间'")private Date createTime;
}
package com.ifly.task.pojo;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;/*** @program: mypro* @description:* @create: 2022-07-22 14:14**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "type")
public class Type {@Id@Column(nullable = false, columnDefinition = "bigint comment '主键id'")@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "name", columnDefinition = "varchar(128) COMMENT '类型名称'")private String name;@Column(name = "code", columnDefinition = "varchar(128) COMMENT '编码'")private String code;}

第四步:实体类对应的 repository

package com.ifly.task.repository;import com.ifly.task.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;/*** @program: mypro* @description: todo* @create: 2022-07-22 14:17**/
@Repository
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}
package com.ifly.task.repository;import com.ifly.task.pojo.Country;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;/*** @program: mypro* @description: todo* @create: 2022-07-22 14:17**/
@Repository
public interface CountryRepository extends JpaRepository<Country, Long>, JpaSpecificationExecutor<Country> {}
package com.ifly.task.repository;import com.ifly.task.pojo.Type;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;import java.util.List;/*** @program: mypro* @description: todo* @create: 2022-07-22 14:17**/
@Repository
public interface TypeRepository extends JpaRepository<Type, Long>, JpaSpecificationExecutor<Type> {/*** 获取所有的类型名称* @return*/@Query(value = "SELECT name FROM type", nativeQuery = true)List<String> findAllType();
}

第五步:写一个controller初始化数据

package com.ifly.task.controller;import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.ifly.task.pojo.Country;
import com.ifly.task.pojo.Type;
import com.ifly.task.pojo.User;
import com.ifly.task.repository.CountryRepository;
import com.ifly.task.repository.TypeRepository;
import com.ifly.task.repository.UserRepository;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.Date;
import java.util.List;/*** @program: easyExcel* @description:* @create: 2022-07-29 14:34**/
@RestController
@RequestMapping("api")
public class InitController {private final UserRepository userRepository;private final CountryRepository countryRepository;private final TypeRepository typeRepository;public InitController(UserRepository userRepository, CountryRepository countryRepository, TypeRepository typeRepository){this.userRepository = userRepository;this.countryRepository = countryRepository;this.typeRepository = typeRepository;}@PostMapping("/init")public void init(){ArrayList<User> userList = new ArrayList<>();userList.add(new User(1L, "赢政", 1, 1));userList.add(new User(2L, "孙尚香", 2, 12));userList.add(new User(3L, "宫本武藏", 1, 3));userList.add(new User(4L, "赢虔", 1, 4));userList.add(new User(5L, "欧阳修", 1, 5));userList.add(new User(6L, "郦食其", 1, 6));userList.add(new User(7L, "贾诩", 1, 7));userList.add(new User(8L, "法正", 1, 8));userList.add(new User(9L, "韩信", 1, 9));userList.add(new User(10L, "黄继光", 1, 10));userList.add(new User(11L, "李狗儿", 3, 11));userList.add(new User(12L, "滨崎步", 2, 12));userRepository.saveAll(userList);List<Country> countryList = new ArrayList<Country>();countryList.add(new Country(1L, "秦国", "嬴渠梁", "商鞅", new Date()));countryList.add(new Country(2L, "赵国", "赵雍", "楼缓", new Date()));countryList.add(new Country(3L, "齐国", "姜小白", "管仲", new Date()));countryList.add(new Country(4L, "魏国", "魏斯", "李悝", new Date()));countryList.add(new Country(5L, "燕国", "燕职", "乐毅", new Date()));countryRepository.saveAll(countryList);ArrayList<Type> typeDtoArrayList = new ArrayList<>();typeDtoArrayList.add(new Type(1L, "君王", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(2L, "勇士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(3L, "武士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(4L, "将军", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(5L, "文士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(6L, "辩士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(7L, "谋士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(8L, "军师", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(9L, "国士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(10L, "烈士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(11L, "男士", UUID.randomUUID().toString()));typeDtoArrayList.add(new Type(12L, "女士", UUID.randomUUID().toString()));typeRepository.saveAll(typeDtoArrayList);}
}

浏览器访问请求:http://localhost/api/init
将上面controller的数据写入到数据库;

二、导出到excel

将数据库中的数据导出到excel,并实现对用户的友好展示

需求:导出user表中的内容和country表中的内容,现在user表中的数据,性别和类型这两列是数字类型,导出的时候需要做一个转换(Converter);而且我要将这两张表的内容导出到一张excel的不同的sheet;还要对导出的user信息,进行主键隐藏,因为有的项目需要根据导入的excel中的主键进行更新,但是这个主键我不想给用户看到;

第一步:设置导出导出到excel的实体类

这些类是告知程序我们导入导出的excel样式,导入导出时的数据结构;

package com.ifly.task.vo;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.ifly.task.converter.GenderConverter;
import com.ifly.task.converter.TypeConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 25) // 头部行高
@ContentRowHeight(value = 15) // 内容行高
@ColumnWidth(value = 20) // 列宽
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 表头样式,水平居中
@HeadFontStyle(fontName = "黑体") // 表头字体
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 内容样式
public class UserExcelVo {// 默认不展示的字段(隐藏字段)@ColumnWidth(value = 0)@ExcelProperty(value = {"用户列表", "编号"})private Long id;@ExcelProperty(value = {"用户列表", "姓名"})private String userName;@ExcelProperty(value = {"用户列表", "性别"})private Integer gender;@ExcelProperty(value = {"用户列表", "类型"})private Integer type;}
package com.ifly.task.vo;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;/*** @program: excel* @description: todo* @create: 2022-07-21 23:28**/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 25) // 头部行高
@ContentRowHeight(value = 15) // 内容行高
@ColumnWidth(value = 20) // 列宽
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 表头样式,水平居中
@HeadFontStyle(fontName = "黑体") // 表头字体
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 内容样式
public class CountryExcelVo {// 默认不展示的字段(隐藏字段)@ColumnWidth(value = 0)@ExcelProperty(value = {"国家列表", "编号"})private Long id;@ExcelProperty(value = {"国家列表", "国名"})private String countryName;@ExcelProperty(value = {"国家列表", "掌权者"})private String power;@ExcelProperty(value = {"国家列表", "丞相"})private String primeMinister;@ExcelProperty(value = {"国家列表", "建朝时间"})@DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")private Date createTime;
}

第二步:设置导入导出到excel的转换器

现在直接从数据库拿到的user数据,性别和人物类型都是数字类型,我们需要在导出的时候,将数据库中查询到的数字(Java代码中看到的)转换成用户能够直观看到的,比如1表示的就是性别男,2表示的是性别女,3表示对性别保密这样;而且当导入excel时,也能把excel中保存的String类型的数据转换成数字,到时候更新或者新增数据就好办了,直接save或者update;

package com.ifly.task.vo;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.ifly.task.converter.GenderConverter;
import com.ifly.task.converter.TypeConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 25) // 头部行高
@ContentRowHeight(value = 15) // 内容行高
@ColumnWidth(value = 20) // 列宽
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 表头样式
@HeadFontStyle(fontName = "黑体")
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 内容样式
public class UserExcelVo {// 默认不展示的字段@ColumnWidth(value = 0)@ExcelProperty(value = {"用户列表", "编号"})private Long id;@ExcelProperty(value = {"用户列表", "姓名"})private String userName;@ExcelProperty(value = {"用户列表", "性别"}, converter = GenderConverter.class)private Integer gender;@ExcelProperty(value = {"用户列表", "类型"}, converter = TypeConverter.class)private Integer type;}

第三步:样式转换器,实现com.alibaba.excel.converters.Converter 接口;

性别转换器【GenderConverter.java】

package com.ifly.task.converter;import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.ifly.task.enums.GenderEnum;/*** @program: mypro* @description: todo* @create: 2022-07-22 11:10**/
public class GenderConverter implements Converter<Integer> {@Overridepublic Class<?> supportJavaTypeKey() {return Integer.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}// 这里我直接使用一个枚举类// 这块是将excel表格中的数据转换成需要存入到数据库中的数据// 比如在excel中出现的别男,我存入数据库的应该是数字1@Overridepublic Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {return GenderEnum.getValue(context.getReadCellData().getStringValue());}// 这里是将数据库中查到的值,比如性别栏是数字1,那么在excel中应该显示 男@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {return new WriteCellData<String>(GenderEnum.getDescribe(context.getValue()));}
}

【性别枚举类】

package com.ifly.task.enums;public enum GenderEnum {NAIL(1, "男"),FEMALE(2, "女"),UNKNOWN(3, "保密");private final Integer value;private final String describe;GenderEnum(Integer value, String detail){this.value = value;this.describe = detail;}public Integer getValue(){return value;}public String getDescribe(){return describe;}/*** 根据value获取describe* @param value* @return*/public static String getDescribe(Integer value) {for (GenderEnum en : GenderEnum.values()) {if (en.getValue().equals(value)) {return en.getDescribe();}}return "error";}/*** 根据describe获取value* @param describe* @return*/public static Integer getValue(String describe){for (GenderEnum en : GenderEnum.values()) {if (en.getDescribe().equals(describe)) {return en.getValue();}}return -1;}}

同上理,我再添加一个 任务类型的转换器 【TypeConverter.java】

package com.ifly.task.converter;import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.ifly.task.enums.TypeEnum;/*** @program: mypro* @description: todo* @create: 2022-07-22 11:10**/
public class TypeConverter implements Converter<Integer> {@Overridepublic Class<?> supportJavaTypeKey() {return Integer.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}@Overridepublic Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {return TypeEnum.getValue(context.getReadCellData().getStringValue());}@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {return new WriteCellData<String>(TypeEnum.getDescribe(context.getValue()));}
}

类型枚举类【TypeEnum.java】

package com.ifly.task.enums;/*** @program: mypro* @description: * @create: 2022-07-22 14:07**/
public enum TypeEnum {EMPEROR(1, "君王"),BRAVE_MAN(2, "勇士"),SAMURAI(3, "武士"),GENERAL(4, "将军"),LITERATI(5, "文士"),TRAIN_SERVICE(6, "辩士"),SOPHIST(7, "谋士"),MILITARY_ADVISER(8, "军师"),UNIQUE(9, "国士"),MARTYR(10, "烈士"),MALE(11, "男士"),FEMALE(12, "女士");private final Integer value;private final String describe;TypeEnum(Integer value, String detail){this.value = value;this.describe = detail;}public Integer getValue(){return value;}public String getDescribe(){return describe;}/*** 根据value获取describe* @param value* @return*/public static String getDescribe(Integer value) {for (TypeEnum en : TypeEnum.values()) {if (en.getValue().equals(value)) {return en.getDescribe();}}return "error";}/*** 根据describe获取value* @param describe* @return*/public static Integer getValue(String describe){for (TypeEnum en : TypeEnum.values()) {if (en.getDescribe().equals(describe)) {return en.getValue();}}return -1;}}

第四步:导出excel

导出到不同的sheet,隐藏主键id,友好的展示在excel中(比如:性别一栏,男就是男,女就是女,不是1和2)

package com.ifly.task.controller;import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.ifly.task.pojo.Country;
import com.ifly.task.pojo.User;
import com.ifly.task.repository.CountryRepository;
import com.ifly.task.repository.TypeRepository;
import com.ifly.task.repository.UserRepository;
import com.ifly.task.vo.CountryExcelVo;
import com.ifly.task.vo.UserExcelVo;
import org.springframework.web.bind.annotation.GetMapping;
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;
import java.util.List;/*** @program: easyExcel* @description:* @author: xuYao2* @create: 2022-07-29 15:27**/
@RestController
@RequestMapping("excel")
public class EasyExcelController {private final UserRepository userRepository;private final CountryRepository countryRepository;public EasyExcelController(UserRepository userRepository, CountryRepository countryRepository){this.userRepository = userRepository;this.countryRepository = countryRepository;}@GetMapping("/export")public void write(HttpServletResponse response) throws IOException {// 设置响应头response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 设置防止中文名乱码String filename = URLEncoder.encode(DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"), "utf-8");// 文件下载方式(附件下载还是在当前浏览器打开)response.setHeader("Content-disposition", "attachment;filename=" +filename + ".xlsx");// easyExcel 获取写出工具ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();/***************************************注意:实际项目中需要导出的数据,可能需要重新拼装成导入导出定义的excelVo;比如我下面的User实体类List集合,是我从数据库直接查出来的,实际项目中我可能只需要id、name、其他的不用导出那我在设置下面的UserExcelVo时,就只设置这两个字段就行,而且数据库中查到的数据list集合中的对象,也要跟excelVo对应上;再通俗点,User和UserExcelVo字段要一模一样;***************************************/List<User> userList = userRepository.findAll();List<Country> countryList = countryRepository.findAll();excelWriter.write(userList,EasyExcel.writerSheet(0, "用户").head(UserExcelVo.class).build());excelWriter.write(countryList,EasyExcel.writerSheet(1, "国家").head(CountryExcelVo.class).build());// 写入数据到excelexcelWriter.finish();}}

浏览器访问:http://localhost/excel/export

导出的excel

三、下拉选择框

需求:导入存在的问题,user对应的sheet,性别和类型这块,用户怎么知道输入什么呢?所以这里必须对excel表格,或者说导出的excel模板做下拉框的限定,用户必须有选择的输入;所以导出的时候,就要对excel某些列做下拉选择框的限定;

第一步:写一个注解,指定excelVo中的哪些字段是做下拉选择框限定的,这里的下拉选择框,可以做成静态的(固定的几项),比如这里的性别,就三种:男、女、保密;动态的(从数据库中获取),比如这里的类型,有很多,需要从数据库获取(这里的类型我也做了枚举限制,其实也可以遍历枚举类型,将类型名放在一个String数组,作为动态下拉选择框);

package com.ifly.task.anno;import com.ifly.task.selector.ExcelDynamicSelect;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 标注导出的列为下拉框类型,并为下拉框设置内容*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {/*** 固定下拉内容*/String[] source() default {};/*** 动态下拉内容*/Class<? extends ExcelDynamicSelect>[] sourceClass() default {};/*** 设置下拉框的起始行,默认为第二行*/int firstRow() default 1;/*** 设置下拉框的结束行,默认为最后一行*/int lastRow() default 0x10000;
}

第二步:动态下拉选择框的接口,然后写一个类型选择下拉框的数据获取;

package com.ifly.task.selector;public interface ExcelDynamicSelect {/*** 获取动态生成的下拉框可选数据* @return 动态生成的下拉框可选数据*/String[] getSource();
}
package com.ifly.task.selector.impl;import com.ifly.task.repository.TypeRepository;
import com.ifly.task.selector.ExcelDynamicSelect;
import com.ifly.task.util.SpringContextUtil;import java.util.List;/*** @program: mypro* @description: todo* @create: 2022-07-22 14:04**/
public class TypeSelector implements ExcelDynamicSelect {@Overridepublic String[] getSource() {TypeRepository repository = SpringContextUtil.getBean(TypeRepository.class);List<String> allType = repository.findAllType();String[] strings = allType.toArray(new String[0]);return strings;}
}

这里使用到工具类:

package com.ifly.task.util;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** @program: mypro* @description: todo* @create: 2022-07-22 13:45**/
@Component
public class SpringContextUtil implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringContextUtil.applicationContext = applicationContext;}// 通过class获取Beanpublic static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}// 通过name以及class获取Beanpublic static <T> T getBean(String name, Class<T> clazz) {return applicationContext.getBean(name, clazz);}}

第三步:给UserExcelVo 加上下拉选择框的注解

package com.ifly.task.vo;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.ifly.task.anno.ExcelSelected;
import com.ifly.task.converter.GenderConverter;
import com.ifly.task.converter.TypeConverter;
import com.ifly.task.selector.impl.TypeSelector;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 25) // 头部行高
@ContentRowHeight(value = 15) // 内容行高
@ColumnWidth(value = 20) // 列宽
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 表头样式
@HeadFontStyle(fontName = "黑体")
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER) // 内容样式
public class UserExcelVo {// 默认不展示的字段@ColumnWidth(value = 0)@ExcelProperty(value = {"用户列表", "编号"})private Long id;@ExcelProperty(value = {"用户列表", "姓名"})private String userName;//静态下拉框@ExcelSelected(source = {"男","女","保密"})@ExcelProperty(value = {"用户列表", "性别"}, converter = GenderConverter.class)private Integer gender;//动态下拉框@ExcelSelected(sourceClass = TypeSelector.class)@ExcelProperty(value = {"用户列表", "类型"}, converter = TypeConverter.class)private Integer type;}

第四步:如何处理这个下拉选择框?

【ExcelSelectedResolve.java】

静态或者动态的获取下拉选择框内容,静态的是注解中 source 属性对应的字符串数组;动态下拉选择框是实现了 ExcelDynamicSelect 接口的实现类,实现类中重写的 getSource() 方法,就是从数据库中动态获取String数组;这里使用到了反射的基本原理;

package com.ifly.task.anno;import com.ifly.task.selector.ExcelDynamicSelect;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;/*** @program: mypro* @description: todo* @author: xuYao2* @create: 2022-07-22 13:35**/
@Data
@Slf4j
public class ExcelSelectedResolve {/*** 下拉内容*/private String[] source;/*** 设置下拉框的起始行,默认为第二行*/private int firstRow;/*** 设置下拉框的结束行,默认为最后一行*/private int lastRow;public String[] resolveSelectedSource(ExcelSelected excelSelected) {if (excelSelected == null) {return null;}// 获取固定下拉框的内容String[] source = excelSelected.source();if (source.length > 0) {return source;}// 获取动态下拉框的内容Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();if (classes.length > 0) {try {ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();String[] dynamicSelectSource = excelDynamicSelect.getSource();if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {return dynamicSelectSource;}} catch (InstantiationException | IllegalAccessException e) {log.error("解析动态下拉框数据异常", e);}}return null;}
}

还需要借助工具类【EasyExcelUtil.java】,解析表头类中的下拉注解。看代码

package com.ifly.task.util;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.ifly.task.anno.ExcelSelected;
import com.ifly.task.anno.ExcelSelectedResolve;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;/*** @program: mypro* @description: todo* @create: 2022-07-22 13:40**/
@Slf4j
public class EasyExcelUtil {/*** 解析表头类中的下拉注解* @param head 表头类* @param <T> 泛型* @return Map<下拉框列索引, 下拉框内容> map*/public static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();// getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性Field[] fields = head.getDeclaredFields();for (int i = 0; i < fields.length; i++){Field field = fields[i];// 解析注解信息ExcelSelected selected = field.getAnnotation(ExcelSelected.class);ExcelProperty property = field.getAnnotation(ExcelProperty.class);if (selected != null) {ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();// 处理下拉框内容String[] source = excelSelectedResolve.resolveSelectedSource(selected);if (source != null && source.length > 0){excelSelectedResolve.setSource(source);excelSelectedResolve.setFirstRow(selected.firstRow());excelSelectedResolve.setLastRow(selected.lastRow());if (property != null && property.index() >= 0){selectedMap.put(property.index(), excelSelectedResolve);} else {selectedMap.put(i, excelSelectedResolve);}}}}return selectedMap;}
}

第五步:在导出代码中,处理下拉选择框

使用工具类,将下拉框做成一个map的形式(列号,下拉选择框String数组),注意列号是从0开始的;

【EasyExcelController.java】

@GetMapping("/export")
public void write(HttpServletResponse response) throws IOException {// 设置响应头response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 设置防止中文名乱码String filename = URLEncoder.encode(DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"), "utf-8");// 文件下载方式(附件下载还是在当前浏览器打开)response.setHeader("Content-disposition", "attachment;filename=" +filename + ".xlsx");// easyExcel 获取写出工具ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();// 设置下拉列表Map<Integer, ExcelSelectedResolve> selectedMap = EasyExcelUtil.resolveSelectedAnnotation(UserExcelVo.class);List<User> userList = userRepository.findAll();List<Country> countryList = countryRepository.findAll();excelWriter.write(userList,EasyExcel.writerSheet(0, "用户").registerWriteHandler(new SelectedSheetWriteHandler(selectedMap)).head(UserExcelVo.class).build());excelWriter.write(countryList,EasyExcel.writerSheet(1, "国家").head(CountryExcelVo.class).build());// 写入数据到excelexcelWriter.finish();}

【SelectedSheetWriteHandler.java】

package com.ifly.task.handler;import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.ifly.task.anno.ExcelSelectedResolve;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;import java.util.Map;/*** @program: mypro* @description: todo* @create: 2022-07-22 13:36**/
@Data
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {private final Map<Integer, ExcelSelectedResolve> selectedMap;@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 这里可以对cell进行任何操作Sheet sheet = writeSheetHolder.getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();selectedMap.forEach((k, v) -> {// 设置下拉列表的行: 首行,末行,首列,末列CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);// 设置下拉列表的值DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());// 设置约束DataValidation validation = helper.createValidation(constraint, rangeList);// 阻止输入非下拉选项的值validation.setErrorStyle(DataValidation.ErrorStyle.STOP);validation.setShowErrorBox(true);validation.setSuppressDropDownArrow(true);validation.createErrorBox("提示", "请输入下拉选项中的内容");sheet.addValidationData(validation);});}
}

最终导出效果:

四、excle导入

@PostMapping("/import")
public void importExcel(@RequestParam("file") MultipartFile file) throws IOException {List<UserExcelVo> userList = EasyExcel.read(file.getInputStream()).head(UserExcelVo.class).sheet(0).doReadSync();List<CountryExcelVo> countryList = EasyExcel.read(file.getInputStream()).head(CountryExcelVo.class).sheet(1).doReadSync();log.info("userList: {}", userList);log.info("country: {}", countryList);}

看控制台:

userList: [UserExcelVo(id=1, userName=赢政, gender=1, type=1), UserExcelVo(id=2, userName=孙尚香, gender=2, type=12), UserExcelVo(id=3, userName=宫本武藏, gender=1, type=3), UserExcelVo(id=4, userName=赢虔, gender=1, type=4), UserExcelVo(id=5, userName=欧阳修, gender=1, type=5), UserExcelVo(id=6, userName=郦食其, gender=1, type=6), UserExcelVo(id=7, userName=贾诩, gender=1, type=7), UserExcelVo(id=8, userName=法正, gender=1, type=8), UserExcelVo(id=9, userName=韩信, gender=1, type=9), UserExcelVo(id=10, userName=黄继光, gender=1, type=10), UserExcelVo(id=11, userName=李狗儿, gender=3, type=11), UserExcelVo(id=12, userName=滨崎步, gender=2, type=12)]country: [CountryExcelVo(id=1, countryName=秦国, power=嬴渠梁, primeMinister=商鞅, createTime=Fri Jul 29 15:19:56 CST 2022), CountryExcelVo(id=2, countryName=赵国, power=赵雍, primeMinister=楼缓, createTime=Fri Jul 29 15:19:56 CST 2022), CountryExcelVo(id=3, countryName=齐国, power=姜小白, primeMinister=管仲, createTime=Fri Jul 29 15:19:56 CST 2022), CountryExcelVo(id=4, countryName=魏国, power=魏斯, primeMinister=李悝, createTime=Fri Jul 29 15:19:56 CST 2022), CountryExcelVo(id=5, countryName=燕国, power=燕职, primeMinister=乐毅, createTime=Fri Jul 29 15:19:56 CST 2022)]

需求:导入刚刚导出的excel,但是对导出的excel做了修改,直接导入excel对数据库中的数据进行修改;

第一步:这里我使用的了mapStruct;导入依赖;

<!-- mapStruct -->
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId><version>1.3.1.Final</version>
</dependency>
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.1.Final</version>
</dependency>

再新建包,写两个接口,注意接口上加上注解:@Mapper(componentModel = "spring")

package com.ifly.task.mapper;import com.ifly.task.pojo.User;
import com.ifly.task.vo.UserExcelVo;
import org.mapstruct.Mapper;
import java.util.List;@Mapper(componentModel = "spring")
public interface UserConvertMapper {List<User> toUserList(List<UserExcelVo> userExcelVoList);
}
package com.ifly.task.mapper;import com.ifly.task.pojo.Country;
import com.ifly.task.vo.CountryExcelVo;
import org.mapstruct.Mapper
import java.util.List;@Mapper(componentModel = "spring")
public interface CountryConvertMapper {List<Country> toCountryList(List<CountryExcelVo> countryExcelVoList);
}

第二步:修改Country 实体类

package com.ifly.task.pojo;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;/*** @program: excel* @description: todo* @author: xuYao2* @create: 2022-07-21 23:28**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "country")
@EntityListeners(AuditingEntityListener.class)
public class Country {@Id@Column(nullable = false, columnDefinition = "bigint comment '主键id'")@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "country_name", columnDefinition = "varchar(128) COMMENT '国家名称'")private String countryName;@Column(name = "power", columnDefinition = "varchar(128) COMMENT '掌权者名字'")private String power;@Column(name = "prime_minister", columnDefinition = "varchar(128) COMMENT '丞相名字'")private String primeMinister;/*** 自动创建时间:https://blog.csdn.net/jj89929665/article/details/119487947*/@Column(name = "create_time", columnDefinition = "datetime COMMENT '国家建立时间'")@CreatedDateprivate Date createTime;
}

还需要在启动类上加上@EnableJpaAuditing 注解

第三步:修改导出接口

@Resource
private UserConvertMapper userConvertMapper;@Resource
private CountryConvertMapper countryConvertMapper;@PostMapping("/import")
public void importExcel(@RequestParam("file") MultipartFile file) throws IOException {List<UserExcelVo> userList = EasyExcel.read(file.getInputStream()).head(UserExcelVo.class).sheet(0).doReadSync();List<CountryExcelVo> countryList = EasyExcel.read(file.getInputStream()).head(CountryExcelVo.class).sheet(1).doReadSync();log.info("userList: {}", userList);log.info("country: {}", countryList);// mapStruct对象转换List<User> toUserList = userConvertMapper.toUserList(userList);List<Country> toCountryList = countryConvertMapper.toCountryList(countryList);// 数据库中的数据List<User> userListInDataBase = userRepository.findAll();List<Country> countryListInDataBase = countryRepository.findAll();// 处理导出的excel中被删除的数据List<Long> userIdsInDataBase = userListInDataBase.stream().map(User::getId).collect(Collectors.toList());List<Long> userIdsInExcel = toUserList.stream().map(User::getId).collect(Collectors.toList());// excel中没有,但数据库中存在的id,是做了删除操作的userIdsInDataBase.removeAll(userIdsInExcel);userIdsInDataBase.forEach(userRepository::deleteById);List<Long> countryIdsInDataBase = countryListInDataBase.stream().map(Country::getId).collect(Collectors.toList());List<Long> countryIdsInExcel = toCountryList.stream().map(Country::getId).collect(Collectors.toList());countryIdsInDataBase.removeAll(countryIdsInExcel);countryIdsInDataBase.forEach(countryRepository::deleteById);userRepository.saveAll(toUserList);countryRepository.saveAll(toCountryList);}

第四步:测试

在postman上导入数据:

查看数据库:


easyExcel导出下拉选择框,多sheet数据excle导入导出相关推荐

  1. EasyExcel增加下拉选择框

    一. 概述 本文将用示例介绍如何用EasyExcel导出下拉框 二. 示例 2.1 编写样式处理类: TitleHandler @Slf4j public class TitleHandler imp ...

  2. jquery.chosen.js下拉选择框美化插件项目实例

    由于之前使用的bootstrap-select插件是建立在bootstrap基础上的,实际使用到项目中的时候,与我们使用的ace-admin(基于bootstrap)存在样式冲突,导致下拉框的样式发生 ...

  3. Bootstrap 表单控件一(单行输入框input,下拉选择框select ,文本域textarea)

    单行输入框,常见的文本输入框,也就是input的type属性值为text.在Bootstrap中使用input时也必须添加type类型,如果没有指定type类型,将无法得到正确的样式,因为Bootst ...

  4. python select模块安装_python+selenium select下拉选择框定位处理方法

    一.前言 总结一下python+selenium select下拉选择框定位处理的两种方式,以备后续使用时查询: 二.直接定位(XPath) 使用Firebug找到需要定位到的元素,直接右键复制XPa ...

  5. 自定义组合控件:下拉选择框

    Spinner 自定义组合控件之下拉选择框 项目概述 下拉选择框主要是通过在EditText 下用PopupWindow 动态显示ListView 控件来实现的.下拉选择框可以方便用户的输入效率,以此 ...

  6. js下拉 selenium_selenium的下拉选择框

    今天总结下selenium的下拉选择框.我们通常会遇到两种下拉框,一种使用的是html的标签select,另一种是使用input标签做的假下拉框. 后者我们通常的处理方式与其他的元素类似,点击或使用J ...

  7. html下拉选择框箭头改为年,CSS自定义select下拉选择框的样式(不用其他标签模拟)...

    今天群里有人问到怎么自定义select下拉选择框的样式,于是群里就展开了激烈的讨论,刚开始一直就是考虑怎样使用纯CSS实现,把浏览器默认的样式覆盖掉,但最后均因兼容问题处理不好而失败告终,最后的解决方 ...

  8. UI标签库专题十一:JEECG智能开发平台 DictSelect (数据字典下拉选择框)

     1. DictSelect (数据字典下拉选择框) 1.1. 参数 属性名 类型 描述 是否必须 默认值 typeGroupCode string 字典分组编码 是 null field str ...

  9. UI标签库专题七:JEECG智能开发平台 ComboBox (下拉选择框)

     1.  ComboBox (下拉选择框) 1.1. 参数 属性名 类型 描述 是否必须 默认值 name string 控件名称 是 null url string 远程数据访问 是 null ...

  10. layui实现select下拉选择框组件(含代码、案例、截图)

    layui实现select下拉选择框组件(含代码.案例.截图) 案例 · 效果图: 全部代码如下: <!DOCTYPE html> <html> <head>< ...

最新文章

  1. 如何双击运行可执行的jar包-包懂
  2. 上传图片时,图片大小不一,设置宽高,图片拉伸,如何处理呢,我来告诉你...
  3. 从 Storm 迁移到 Flink,美团外卖实时数仓建设实践
  4. 算法:动态规划算法的最佳实践-背包问题
  5. 95-140-118-源码-transform-算子connect
  6. 一张速查表看懂Git命令,搞定版本控制照做就ok丨新手福利
  7. 提升网络销售转化率的10种方法
  8. 移动应用市场统计分析
  9. 用react native 做的一个推酷客户端
  10. 软件项目与产品的区别与联系_软件产品和软件过程之间的区别和关系
  11. python 绝对值计算
  12. 维护和维修涉密计算机网络 必须严格采取,安全保密管理员主要负责涉密网络的日常安全保密管理工作,包括()。A.涉密网络的日常运行维护工 - 普法考试题库问答...
  13. DFS----深度优先搜索与记忆化数组例题分析
  14. UnityWebGL发布包问题及IIS配置
  15. v-model中修饰符lazy,number, trim的作用
  16. Java中的私有权限(private)
  17. 华为S9300核心交换机链路聚合配置实例
  18. 统计学,机器学习,深度学习,数据挖掘的联系
  19. 锐捷 Smartweb管理系统 密码信息泄露漏洞
  20. DexGuard 与 ProGuard区别

热门文章

  1. 四象限法推导lm曲线_四象限法分析
  2. 碳足迹审核的计算有几种方法?
  3. 【最大公约数】欧几里得算法
  4. python爬网站信息_一个爬取实习僧网站信息的爬虫
  5. 网页编程设计常用的各类标签
  6. Win7开机提示任务管理器已停止工作解决方法
  7. 分段式多级离心泵_分段式多级离心泵的组装与调整
  8. linux设备i2c优先级,Linux设备之I2C
  9. 企业微信登录不了怎么办 企业微信无法登录的原因及解决方法
  10. 2020年美国新冠肺炎疫情数据分析