SpringBoot最简单好用的导出导入功能,拿来即用
作者:mmseoamin日期:2023-12-18

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 拿来吧你
  • 一、接口说明
  • 二、依赖
  • 三、导出工具类
  • 四、导入工具类
  • 五、实体类的改造
    • BaseEntity.java
    • 六、控制层使用
    • 七、效果
      • 数据库表
      • 1. 下载模板
      • 2. 导出
      • 3. 导入
      • 总结

        拿来吧你

        提示:粘贴即可用

        SpringBoot 项目整合的导入导出接口。


        一、接口说明

        项目pom.xml 文件添加依赖,编写封装类即可。依赖、导入导出的封装类以及使用方法,以下文章都会一一列举,十分方便,已经经过测试,复制粘贴用起来吧。

        二、依赖

        提示:这里给的是关于导出导出的依赖。

        代码如下(示例):

        	 
                
                    javax.persistence
                    javax.persistence-api
                    2.2
                
                
                
                    org.apache.poi
                    poi
                    4.1.2
                
                
                
                    org.apache.poi
                    poi-ooxml
                    4.1.2
                
                
                    org.apache.commons
                    commons-lang3
                    3.12.0
                
                
                
                    com.alibaba
                    fastjson
                    2.0.21
                
        

        三、导出工具类

        提示: 复制即可用

        exportData()方法有三个参数:

        1. response :请求接口浏览器直接新开窗口下载导出的文件
        2. List dataList : 用户数据,比如导出User表数据,那么将从数据库查询的用户数据作为这第二个参数
        3. Class EntityClass : 实体类字节码,导出工具类直接根据实体类的字段和数据库的字段一一映射,将User.class作为这第三个参数。

        代码如下(示例):

        import org.apache.poi.ss.usermodel.*;
        import javax.persistence.Column;
        import javax.persistence.Table;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        import java.lang.reflect.Field;
        import java.net.URLEncoder;
        import java.time.LocalDateTime;
        import java.time.format.DateTimeFormatter;
        import java.util.Date;
        import java.util.List;
        import java.util.Locale;
        import java.util.Objects;
        public class ExportUtils {
            public static  void exportData(HttpServletResponse response, List dataList, Class EntityClass) throws IOException {
                // 创建Excel工作簿
                Workbook workbook = WorkbookFactory.create(true);
                // 创建工作表
        //        Sheet sheet = workbook.createSheet("数据表");
                Sheet sheet = workbook.createSheet(EntityClass.getSimpleName());
                // 设置标题行的字体样式
                Font font = workbook.createFont();
                font.setBold(true); // 将字体设置为加粗样式
                // 获取表名
                Table tableAnnotation = EntityClass.getAnnotation(Table.class);
        //        String tableName = EntityClass.getSimpleName(); // 获取的是UserDO(类的名字)
                String tableName = "data";
                if (tableAnnotation != null) {
                    tableName = tableAnnotation.name();
                }
                /**
                 *    创建标题行
                 * 1. 要根据实体类的 @Column(name = "姓名") 注解中的 name 值来设置标题行,
                 * 2. 你可以通过反射获取实体类的字段名称。
                 * 3. 可以使用 Class.getDeclaredFields() 方法来获取所有的字段,
                 * 4. 然后使用 Field.getAnnotation() 方法获取 @Column 注解,最后通过 name() 方法获取注解中设置的名称。
                 */
                Row headerRow = sheet.createRow(0);
                Field[] fields = EntityClass.getDeclaredFields();
                // 数据不为空,写数据,为空,下载模板,模板无序号
                int columnIndex = 0;
                for (Field field : fields) {
                    Column columnAnnotation = field.getAnnotation(Column.class);
                    if (columnAnnotation != null) {
                        String columnName = columnAnnotation.name();
                        
                        // 排除id、createTime、updateTime、isDeleted等属性的导出
                        if (Objects.isNull(dataList)) {
                            if (!("id".equals(field.getName()) || "createTime".equals(field.getName())
                                    || "updateTime".equals(field.getName()) || "status".equals(field.getName())
                                    || "isDeleted".equals(field.getName()) || "icon".equals(field.getName()) )) {
                                Cell headerCell = headerRow.createCell(columnIndex);
                                headerCell.setCellValue(columnName);
                                // 应用字体样式到标题单元格
                                CellStyle cellStyle = workbook.createCellStyle();
                                cellStyle.setFont(font);
                                headerCell.setCellStyle(cellStyle);
                                columnIndex++;
                            }
                        } else {
                                Cell headerCell = headerRow.createCell(columnIndex);
                                headerCell.setCellValue(columnName);
                                // 应用字体样式到标题单元格
                                CellStyle cellStyle = workbook.createCellStyle();
                                cellStyle.setFont(font);
                                headerCell.setCellStyle(cellStyle);
                                columnIndex++;
                        }
                    }
                }
                /**
                 * 1. 可以使用反射获取实体类的字段数量,
                 * 2. 然后在循环中根据字段数来创建单元格
                 */
                // 数据不为空-填充数据到Excel表格
                // 数据为空,下载模板
                if (Objects.nonNull(dataList)) {
                    int rowNumber = 1;
                    for (T data : dataList) {
                        Row row = sheet.createRow(rowNumber);
                        for (int i = 0; i < fields.length; i++) {
                            Field field = fields[i];
                            Column columnAnnotation = field.getAnnotation(Column.class);
                            if (columnAnnotation != null) {
                                Cell cell = row.createCell(i);
                                setCellValue(cell, data, field);
                            }
                        }
                        rowNumber++;
                    }
                }
                // 设置响应头信息
                response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                response.setCharacterEncoding("UTF-8");
                String encodedTableName = URLEncoder.encode(tableName, "UTF-8");
                String filename = encodedTableName + ".xlsx";
                response.setHeader("Content-Disposition", "attachment; filename=" + filename);
                // 将Excel写入响应输出流
                workbook.write(response.getOutputStream());
                // 关闭工作簿
                workbook.close();
            }
            // 调用方法 setCellValue() 在 setCellValue() 方法中根据字段的类型来设置单元格的值
            private static  void setCellValue(Cell cell, T data, Field field) {
                field.setAccessible(true);
                try {
                    Object value = field.get(data);
                    // 根据属性的类型来设置单元格的值
                    if (value instanceof Integer) {
                        cell.setCellValue((Integer) value);
                    } else if (value instanceof String) {
                        cell.setCellValue((String) value);
                    } else if (value instanceof Boolean) {
                        cell.setCellValue((Boolean) value);
                    } else if (value instanceof Date) {
                        DateTimeFormatter sourceFormatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH);
                        DateTimeFormatter targetFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                        LocalDateTime dateTime = LocalDateTime.parse(value.toString(), sourceFormatter);
                        String convertedTime = dateTime.format(targetFormatter);
                        cell.setCellValue(convertedTime);
                    } else if (value instanceof Double) {
                        cell.setCellValue(value.toString());
                    } else {
                        // 处理其他类型的值
                    }
                } catch (IllegalAccessException e) {
                    // 处理异常情况
                    e.printStackTrace();
                }
            }
        }
        

        四、导入工具类

        提示:导入即是上传文件xlsx文件,根据导入的数据转为Java语言,再做其他的操作,比如插入数据库。

        参数解析:

        1. MultipartFile file : 上传到导入接口的文件
        2. Class entityClass : 比如是导入User类,则传入 User.class 作为参数
        import org.apache.poi.ss.usermodel.*;
        import org.springframework.web.multipart.MultipartFile;
        import javax.persistence.Column;
        import java.lang.reflect.Field;
        import java.text.DateFormat;
        import java.text.SimpleDateFormat;
        import java.util.*;
        public class ImportUtil {
            public static  List importEntities(MultipartFile file, Class entityClass) throws Exception {
                // 创建Excel工作簿
                Workbook workbook = WorkbookFactory.create(file.getInputStream());
                // 获取第一个工作表
                Sheet sheet = workbook.getSheetAt(0);
                // 获取标题行的列名
                Row titleRow = sheet.getRow(0);
                List columnNames = getColumnNames(titleRow);
                // 创建实体对象列表
                List entities = new ArrayList<>();
                // 迭代每一行(跳过标题行)
                Iterator iterator = sheet.iterator();
                iterator.next(); // 跳过标题行
                while (iterator.hasNext()) {
                    Row row = iterator.next();
                    // 读取单元格数据并创建实体对象
                    T entity = createEntityFromRow(row, columnNames, entityClass);
                    entities.add(entity);
                }
                // 关闭工作簿
                workbook.close();
                return entities;
            }
            // 获取列名
            private static List getColumnNames(Row row) {
                List columnNames = new ArrayList<>();
                for (Cell cell : row) {
                    columnNames.add(getStringCellValue(cell));
                }
                return columnNames;
            }
            // 根据行数据创建实体对象
            // 对
            private static  T createEntityFromRow(Row row, List columnNames, Class entityClass) throws Exception {
                T entity = entityClass.getDeclaredConstructor().newInstance();
                Field[] fields = entityClass.getDeclaredFields();
                for (int i = 0; i < columnNames.size(); i++) {
                    String columnName = columnNames.get(i);
                    String cellValue = getStringCellValue(row.getCell(i));
                    for (Field field : fields) {
                        field.setAccessible(true);
                        Column columnAnnotation = field.getAnnotation(Column.class);
                        if (columnAnnotation != null && columnAnnotation.name().equals(columnName)) {
                            setFieldValue(entity, field, cellValue);
                            break;
                        }
                    }
                }
                return entity;
            }
            // 获取单元格的字符串值
            private static String getStringCellValue(Cell cell) {
                if (cell == null) {
                    return null;
                }
                String cellValue;
                // 根据单元格类型进行值转换
                switch (cell.getCellType()) {
                    case STRING:
                        cellValue = cell.getStringCellValue();
                        break;
                    case NUMERIC:
                        // 判断是否为日期类型
                        if (DateUtil.isCellDateFormatted(cell)) {
                            DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                            cellValue = sdf.format(cell.getDateCellValue());
                        } else {
                            // 将数字类型转换为字符串类型
                            cell.setCellType(CellType.STRING);
                            cellValue = cell.getStringCellValue();
                        }
                        break;
                    case BOOLEAN:
                        cellValue = String.valueOf(cell.getBooleanCellValue());
                        break;
                    case FORMULA:
                        // 如果公式计算结果为字符串类型,则获取字符串值
                        if (cell.getCachedFormulaResultType() == CellType.STRING) {
                            cellValue = cell.getRichStringCellValue().getString();
                        }
                        // 如果公式计算结果为数字类型,则获取数字值并转换为字符串
                        else if (cell.getCachedFormulaResultType() == CellType.NUMERIC) {
                            cellValue = String.valueOf(cell.getNumericCellValue());
                        }
                        // 如果公式计算结果为布尔类型,则获取布尔值并转换为字符串
                        else if (cell.getCachedFormulaResultType() == CellType.BOOLEAN) {
                            cellValue = String.valueOf(cell.getBooleanCellValue());
                        } else {
                            cellValue = "";
                        }
                        break;
                    default:
                        cellValue = "";
                        break;
                }
                return cellValue;
            }
            // 设置实体类属性的值
            private static  void setFieldValue(T entity, Field field, String cellValue) throws Exception {
                field.setAccessible(true);
                Class fieldType = field.getType();
                // 根据属性的类型进行赋值
                if (fieldType == String.class) {
                    field.set(entity, cellValue);
                } else if (fieldType == Integer.class) {
                    field.set(entity, Integer.valueOf(cellValue));
                } else if (fieldType == Double.class) {
        //            field.set(entity, Double.valueOf(cellValue));
                    field.set(entity, cellValue);
                }
                // 在此处可以根据需要添加其他类型的赋值判断
                else {
                    field.set(entity, null);
                }
            }
        }
        

        五、实体类的改造

        这一步和导入导出息息相关,因为得根据实体类属性的注解来决定导出的列名是什么,表名是什么。以User类为例,看我操作。

        • 主要看@Table(name = “用户信息表”)和@Column(name = “姓名”)
        • @Data是lombok的,@TableName(“tb_user”)是mybatisplus指定数据库表的,这里只是解释一下,与导出导入无关,避免不知道的博友。
        • 还有细心的朋友看见我实体类继承 BaseEntity.java,这是我写的一个公共实体类,主要写表中公共有的字段,避免在多张表重复写相同的属性。可以在下面了解一下。
          import javax.persistence.Column;
          import javax.persistence.Table;
          @Data
          @TableName("tb_user")
          @Table(name = "用户信息表")
          public class UserDO extends BaseEntity {
              @Column(name = "姓名")
              private String username;
              @Column(name = "性别")
              private String sex;
              @Column(name = "密码")
              private String password;
              @Column(name = "邮箱")
              private String email;
              @Column(name = "地址")
              private String address;
          }
          

          BaseEntity.java

          继承这个类之后,User实体类就有id 的属性了,也有其他的字段,比如status, create_time等。这个类也要用@Column(name = “序号”) 标注。

          import com.baomidou.mybatisplus.annotation.IdType;
          import com.baomidou.mybatisplus.annotation.TableId;
          import lombok.Data;
          import javax.persistence.Column;
          import java.io.Serializable;
          @Data
          public class BaseEntity implements Serializable {
              @TableId(type = IdType.AUTO)
              @Column(name = "序号")
              private Integer id;
          }
          

          六、控制层使用

          准备工作做好之后,就是调用接口了,还是以User实体类为例,看我操作。

          @RestController
          @RequestMapping("/api/users")
          @ResponseBody
          public class UserController {
          	/**
               * 下载数据模板
               * 模块的原因是根据模板填写数据,上传的数据才不会出差错
               * 为什么这样?具体原因你懂的
               */
              @Transactional
              @GetMapping("/download/template")
              public void downloadTemplate(HttpServletResponse response) throws IOException {
              	// 调用接口,并传入空数据,在导出已经做出判断,
              	// 说明这个接口是下载模板,不会往表格里面写数据
                  ExportUtils.exportData(response, null , UserDO.class);
              }
              /**
               * 导出
               */
              @Transactional
              @GetMapping("/export")
              public void exportRole(HttpServletResponse response) throws IOException {
              	// 数据库获取数据,Mybatis-plus的方法,不知道的可以学习学习MP
                  List userDOList = userService.list();
          		// 调用导出工具类的方法,传入对应的参数,简单吧
                  ExportUtils.exportData(response, userDOList , UserDO.class);
              }
              /**
               * 导入
               */
              @Transactional
              @PostMapping("/import")
              public void importsEntity(@RequestParam("file") MultipartFile file) throws Exception {
                  // 调用ImportUtil工具类来获取实体对象列表
                  // 传入接口获取的文件和实体类,就可以获取到数据,简单吧
                  List entities = ImportUtil.importEntities(file, UserDO.class);
                  // 在这里处理导入数据的逻辑
                  for (UserDO entity : entities) {
                      // 执行对数据的操作,例如保存到数据库等
                      System.out.println("导入的数据:" + entity);
                      boolean save = userService.save(entity);
                      if (save) {
                         System.out.println("插入  " + entity + "   成功!");
                     } else {
                         System.out.println("插入  " + entity + "   失败!");
                     }
                  }
              }
          }
          

          七、效果

          数据库表

          SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第1张

          1. 下载模板

          • 浏览器 URL 输入地址并回车

            SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第2张

            SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第3张

            • 查看模板并没有id的属性打印,导出工具类判断了。测试正确。

              SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第4张


              2. 导出

              • URL输入地址并回车

                SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第5张

                SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第6张

                • 查看内容:数据正确,且与数据库表字段对应。而且注意一点,id只是在下载模板的时候不打印,导出我并没有设置不打印,看个人需求,你也可以自己改一下。

                  SpringBoot最简单好用的导出导入功能,拿来即用,![在这里插入图片描述](https://img-blog.csdnimg.cn/befd7234c6bb4999a84d0c200faffa2d.pn,第7张


                  3. 导入

                  • 接下来用下载的模板进行写入数据,然后调用导入接口,获取数据。

                  • 数据这样测试,测试在后端接口是否能够正确接收空值,是否有错位。

                    SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第8张

                  • 清空控制台:方便查看打印的数据,无意义,只是表演需要

                    SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第9张

                  • 我这里使用 postman 工具请求:注意我框起来的点即可

                    SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第10张

                  • 铛铛铛铛:看这么几条,空的值都为空,对应的值都对应上

                    SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第11张

                  • 再看数据库:完美!!!扣 666

                    SpringBoot最简单好用的导出导入功能,拿来即用,在这里插入图片描述,第12张

                    ![在这里插入图片描述](https://img-blog.csdnimg.cn/602c547646c04d7a9d66013738ce7362.png

                    总结

                    提示:觉得🐂B的扣个 666

                    惊不惊喜,意不意外!!!!