java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除

工作中设计树结构增删改查,导入,导出操作,搜索 POI导入导出树结构Excle 相关博客较少,故写博客用以记录分享。


文章目录

  • 一、表结构设计,导入导出模板。
  • 二、递归查询树结构
    • 1.思路如下
    • 2.代码示例
  • 二、新增树结构
    • 思路如下
  • 三.树结构修改。
    • 思路如下
  • 四、递归删除树结构
    • 1.思路如下
    • 2.代码示例
  • 五、树结构递归导出Excel
    • 1.思路如下
    • 2.代码示例
  • 六、导入树结构Excel
    • 1.思路如下
    • 2.代码示例
  • 吹牛逼结束,感谢观看

一、表结构设计,导入导出模板。

注: 博主树结构为8级结构,因业务关系,故分为两张表。此表分为4级结构如下:
楼栋 — 单元 — 楼层 — 房间

导入导出Excel表格第一列项目名不在此表范围内,忽略此列即可

表仅截取几个树结构相关字段,业务字段根据需求自己添加即可。
在这里插入图片描述
导出Excel样式如下。
java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除_第1张图片

导入Excel模板样式如下:
java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除_第2张图片
java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除_第3张图片

二、递归查询树结构

1.思路如下

  1. 因博主结构层级较多分为两级处理,所以副表可理解为详情表,详情表树结构数据均对应有主表项目id。
  2. 入参:项目id 根据入参先获取此项目下所有子级数据集合(包含楼栋、单元、楼层、房间)。
  3. 返回实体需包含子级List字段 与 自定义获取所有叶子节点方法
private List<****> children = new ArrayList<SysDeptInfoTreeVo>();

public int getLeftNum(){
    int count = 0 ;
    if (children.isEmpty()){
        return 1;
    }else{
        for (****  s : children){
            count+= s.getLeftNum();
        }
    }
    return count;
}

2.代码示例

        //根据项目id获取此项目下全部数据
   		List<SysDeptInfoTreeVo> sysDeptInfoTreeVoList = deptInfoMapper.selectListByDeptId(sysDeptInfoDto.getDeptId());
        //获取父节点
        List<SysDeptInfoTreeVo> collect = sysDeptInfoTreeVoList.stream().filter(sysDeptInfoTreeVo -> sysDeptInfoTreeVo.getParentId() == 0).map(s -> {
            s.setChildren(getChildren(s, sysDeptInfoTreeVoList));
            return s;
        }).sorted(Comparator.comparing(SysDeptInfoTreeVo::getId)).collect(Collectors.toList());
     
  • 代码解读:
  • 从副表即详情表中先获取此项目下所有数据集合。
  • 过滤获取第一级节点,第一级节点无父节点,获取各个第一级节点后设置子节点调用递归方法即可。
 /**
     * 递归查询子节点
     * @param sysDeptInfoTreeVo  根节点
     * @param sysDeptInfos   所有节点
     * @return 根节点信息
     */
    public static List<SysDeptInfoTreeVo> getChildren(SysDeptInfoTreeVo sysDeptInfoTreeVo, List<SysDeptInfoTreeVo> sysDeptInfos) {
        List<SysDeptInfoTreeVo> children = sysDeptInfos.stream().filter(s -> sysDeptInfoTreeVo.getId().equals(s.getParentId())).map(sysDeptInfo -> {
            System.out.println("'sysDeptInfo' = " + sysDeptInfo.toString());
            sysDeptInfo.setChildren(getChildren(sysDeptInfo,sysDeptInfos));
            return sysDeptInfo;
        }).sorted(Comparator.comparing(SysDeptInfoTreeVo::getId)).collect(Collectors.toList());
        return children;
    }

二、新增树结构

思路如下

  • 新增入参实体需有 parentId 父节点id

  • 新增根据具体业务进行校验

  • 新增需判断是否为第一级节点,
    若是第一级节点,则 父节点id为0 祖级列表为 0 , 等级为 1
    否则 根据 父节点id 先查询父节点,从父节点获取id作为 parentId ,父节点祖级列表+父节点id 作为祖级列表参数,等级 为 父节点等级+1

  • 新增操作分级处理即可,无示例代码

三.树结构修改。

思路如下

  • 修改操作,需根据实际业务修改,博主修改无修改级别操作,故仅为一些校验后直接update。

四、递归删除树结构

1.思路如下

  • 入参:某根节点id,只要获取到此根节点所有子孙级数据,全部删除即可。

2.代码示例

        //根据id获取此项目
        SysDeptInfo sysDeptInfo = deptInfoMapper.selectById(id);
        Assert.isNull(sysDeptInfo,"未查询到项目信息");
        //获取此项目子级集合
        List<SysDeptInfo> sysDeptInfoList = deptInfoMapper.selectList(new QueryWrapper<SysDeptInfo>().eq("dept_id",sysDeptInfo.getDeptId()));
        //此项目下所有子孙级list
        List<SysDeptInfo> list = new ArrayList();
        list.add(sysDeptInfo);
        //调用递归方法
        list = getChildrenList(sysDeptInfo, sysDeptInfoList,list);
     
  • 代码解读:
  • 首先根据某根节点id获取此条数据,然后根据此数据所属项目id即可获取和此根节点所属同一项目下各个级别数据集合,此集合必包含此条数据下的子孙数据。
  • 创建返回list ,此list包含所有要删除的数据集合,包含此节点。
  • 调用递归方法
    /**
     * 递归查询子节点
     * @param sysDeptInfo  根节点
     * @param  sysDeptInfoList 与根节点同属一个项目下的所有数据list
     * @param  list 返回list
     * @return sysDeptInfoList   所有子节点的list集合
     */
    public static List<SysDeptInfo> getChildrenList(SysDeptInfo sysDeptInfo, List<SysDeptInfo> sysDeptInfoList,List<SysDeptInfo> list) {
        List<SysDeptInfo> resList = sysDeptInfoList.stream().filter(s -> sysDeptInfo.getId().equals(s.getParentId())).map(s -> {
            list.add(s);
            List<SysDeptInfo> childrenList = getChildrenList(s, sysDeptInfoList, list);
            return s;
        }).collect(Collectors.toList());
        return list;
    }
  • 获取所有需删除的list集合后,从表中删除即可。

Excel导入导出参考下方链接根据需求修改完善即可: Java POI完成Excel导入导出示例
参考博主poi版本较低,若用4.开头poi需修改版本不一致某些方法修改的问题,下方代码已修正。

五、树结构递归导出Excel

1.思路如下

  • 导出Excel尚未进行单元格合并操作,哪个大佬写好的话,不介意的话分享至评论区,感谢。
  • 已订正,导出Excel已添加合并单元格功能
  • 已订正,导出Excel层级多表格样式错乱问题
  • 入参:递归查询树结构的 结果Vo

2.代码示例

  • 代码解读:
  • 只需关注 exportTaskSumPoi 和 ExcelChildrenNew 这个方法即可。
  • exportTaskSumPoi 方法从第三行开始创建行、单元格目的:因为博主结构分为主副表,主表叶子节点为副表所属项目,例如 主表叶子精确到—爱情公寓,那么副表 第一级节点 从 楼栋开始,即爱情公寓第一栋楼,只不过博主多加一个字段 项目id,指定这些楼栋,单元,楼层,房间属于某一项目,例:
  • 主表中 爱情公寓id为 105
    java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除_第4张图片
  • 副表中加上dept_id字段用来指定属于哪个项目
    java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除_第5张图片
  • 回归第三行开始创建行、单元格目的从主表中获取项目信息并将项目信息放在第三行第一个单元格中,关注点为下面递归方法ExcelChildrenNew
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.List;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.zensun.system.domain.vo.SysDeptInfoTreeVo;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;


/**
 * @program: family-file-pc-b
 * @description: Excel树结构导出Util
 * @author: LYK
 * @create: 2021-05-20 08:37
 **/
@Component
public class ExcelExportUtil {

    private int cols = 9;//excel里表格列

    private String sheetTitle = "项目房间信息";

    public void setCols(int cols) {
        this.cols = cols;
    }

    public void setSheetTitle(String sheetTitle) {
        this.sheetTitle = sheetTitle;
    }

    /**
     * POI : 导出数据,存放于Excel中
     *
     * @param os
     *            输出流 (action: OutputStream os = response.getOutputStream();)
     * @param sysDeptInfoTreeVo
     *            要导出的数据(根数据)
     */
    public  void exportTaskSumPoi(OutputStream os, SysDeptInfoTreeVo sysDeptInfoTreeVo) {
        //取出数据源;
        String headTitle = sheetTitle;//标题;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        try {
            // 创建Excel工作薄
            Workbook book = null;
            try {
                book = new XSSFWorkbook();//excell2007
            } catch (Exception ex) {
                book = new HSSFWorkbook();//excell2003
            }
            // 在Excel工作薄中建一张工作表;
            Sheet sheet = book.createSheet(sheetTitle);
            //设置标题和表头;
            createTitle(book, sheet, headTitle);
            createHead(book, sheet);

            int startCellCount = -1 ; //控制第几个单元格开始字段
            int endCellCount = 0;
            int rowCount = 2 ; //控制创建行数字段
            //从第三行开始创建一行  --- 项目信息
            Row row = sheet.createRow(rowCount);
            //创建一个单元格 ----  项目信息
            Cell cell0 = row.createCell(0);
            cell0.setCellValue(sysDeptInfoTreeVo.getDeptName()==null?"null":sysDeptInfoTreeVo.getDeptName());	
            //获取第一级所有子节点数量,+1是单元格 是从第三行开始
            int i = sysDeptInfoTreeVo.getLeftNum()+ 1;
            //合并项目单元格 四个参数分别为 第一个单元格的行号,第二个单元格行号,第一个单元格列号,第二个单元格的列号
            sheet.addMergedRegion(new CellRangeAddress(2,sysDeptInfoTreeVo.getLeftNum()+1,0,0));
            // 获取楼栋信息
            ExcelChildrenNew(row,sysDeptInfoTreeVo, sheet, startCellCount,endCellCount,rowCount);
            // 写入数据 把相应的Excel 工作簿存盘
            book.write(os);
            book.close();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 递归获取子级并对子级设置行列
     *
     * @param sheet
     */
    private void ExcelChildrenNew(Row row,SysDeptInfoTreeVo sysDeptInfoTreeVo, Sheet sheet, int startCellCount,int endCellCount,int rowCount) {
        Row newRow = row;
        //各自+2目的  各个子级占用2个单元格。
        startCellCount= startCellCount+2;
        endCellCount= endCellCount+2;
        List<SysDeptInfoTreeVo> children = sysDeptInfoTreeVo.getChildren();
        if (CollectionUtils.isNotEmpty(children)){
            for (int i=0; i<children.size(); i++){
                if (i==0){
                //此处表明 第一个子节点应该与父节点在Excel同一行
                    Cell cell0 = newRow.createCell(startCellCount);
                    Cell cell1 = newRow.createCell(endCellCount);
                    cell0.setCellValue(children.get(i).getDeptName()==null?"null":children.get(i).getDeptName());
                    cell1.setCellValue(children.get(i).getRoomTypeName()==null?"null":children.get(i).getRoomTypeName());
                    //只有插入的行号< 行号+所有子节点的数量-1才可合并 即 不是叶子节点才能合并
                     if (rowCount<rowCount+children.get(i).getLeftNum()-1){
                        sheet.addMergedRegion(new CellRangeAddress(rowCount,rowCount+children.get(i).getLeftNum()-1,startCellCount,startCellCount));
                        sheet.addMergedRegion(new CellRangeAddress(rowCount,rowCount+children.get(i).getLeftNum()-1,endCellCount,endCellCount));
                    }
                }else{
                //此处表示 获取所有上级节点的叶子节点数量作为行数 for循环为订正内容删除for即可
                //   for (int j = 1 ; j<= i ; j++){  
                        rowCount += children.get(j-1).getLeftNum();
                //    }
                    newRow = sheet.createRow(rowCount);
                    Cell cell0 = newRow.createCell(startCellCount);
                    Cell cell1 = newRow.createCell(endCellCount);
                    cell0.setCellValue(children.get(i).getDeptName()==null?"null":children.get(i).getDeptName());
                    cell1.setCellValue(children.get(i).getRoomTypeName()==null?"null":children.get(i).getRoomTypeName());
                    if (rowCount<rowCount+children.get(i).getLeftNum()-1){
                        sheet.addMergedRegion(new CellRangeAddress(rowCount,rowCount+children.get(i).getLeftNum()-1,startCellCount,startCellCount));
                        sheet.addMergedRegion(new CellRangeAddress(rowCount,rowCount+children.get(i).getLeftNum()-1,endCellCount,endCellCount));
                    }
                }
                ExcelChildren(newRow,children.get(i),sheet,startCellCount,endCellCount,rowCount);
            }
        }
    }


    /**
     * 给excel设置标题
     *
     * @param sheet
     */
    public void createTitle(Workbook book, Sheet sheet, String headTitle) {
        CellStyle style = createTitleStyle(book);
        Row row = sheet.createRow(0);// 创建第一行,设置表的标题;
        row.setHeightInPoints(36);//设置行的高度是34个点
        Cell cell = row.createCell(0);
        cell.setCellValue(headTitle);
        cell.setCellStyle(style);
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, cols-1));//第一行跨表所有列;
    }

    /**
     * 设置导出表的表头
     *
     * @param book
     * @param sheet
     */
    private void createHead(Workbook book, Sheet sheet) {
        // 设置单元格格式(文本)
        // 第二行为表头行
        String title = "";
        CellStyle style = createHeadStyle(book);

        Row row = sheet.createRow(1);// 创建第一行
        row.setHeightInPoints(22);//设置行的高度是20个点
        for (int j = 0; j < cols; j++) {
            Cell cell = row.createCell(j);
            cell.setCellType(CellType.STRING);
            if (j == 0) {
                title = "项目名";
                sheet.setColumnWidth(j, title.getBytes().length * 2 * 256);
            }
            if (j == 1) {
                title = "楼栋";
                sheet.setColumnWidth(j, title.getBytes().length * 2 * 256);
            }
            if (j == 2) {
                title = "业态";
            }
            if (j == 3) {
                title = "单元";
                sheet.setColumnWidth(j, title.getBytes().length * 2 * 256);
            }
            if (j == 4) {
                title = "业态";
            }
            if (j == 5) {
                title = "楼层";
            }
            if (j == 6) {
                title = "业态";
            }
            if (j == 7) {
                title = "房间";
            }
            if (j == 8) {
                title = "业态";
            }
            cell.setCellValue(title);
            cell.setCellStyle(style);
        }
    }

    /**
     * 创建标题样式
     * @param book
     * @return
     */
    public CellStyle createTitleStyle(Workbook book) {
        CellStyle cellStyle = book.createCellStyle();
        cellStyle.setBorderTop(BorderStyle.THIN);//上边框
        cellStyle.setBorderBottom(BorderStyle.THIN);//下边框
        cellStyle.setBorderLeft(BorderStyle.THIN);//左边框
        cellStyle.setBorderRight(BorderStyle.THIN);//右边框
        cellStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
        Font font = book.createFont();
        font.setFontHeightInPoints((short) 20); // 字体大小
        font.setFontName("宋体");
        cellStyle.setFont(font);
        return cellStyle;
    }

    /**
     * 创建表头样式
     * @param book
     * @return
     */
    public CellStyle createHeadStyle(Workbook book) {
        CellStyle cellStyle = book.createCellStyle();
        cellStyle.setBorderTop(BorderStyle.THIN);//上边框
        cellStyle.setBorderBottom(BorderStyle.THIN);//下边框
        cellStyle.setBorderLeft(BorderStyle.THIN);//左边框
        cellStyle.setBorderRight(BorderStyle.THIN);//右边框
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 填充单元格
        cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_TURQUOISE.getIndex()); // 填浅蓝色
        cellStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
        Font font = book.createFont();
        font.setFontHeightInPoints((short) 11); // 字体大小
        font.setFontName("黑体");
        cellStyle.setFont(font);
        return cellStyle;
    }

    /**
     * 创建内容部分前8列单元格样式
     * @param book
     * @return
     */
    public CellStyle createContentStyle(Workbook book) {
        CellStyle cellStyle = book.createCellStyle();
        cellStyle.setBorderTop(BorderStyle.THIN);//上边框
        cellStyle.setBorderBottom(BorderStyle.THIN);//下边框
        cellStyle.setBorderLeft(BorderStyle.THIN);//左边框
        cellStyle.setBorderRight(BorderStyle.THIN);//右边框
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 填充单元格
        cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_TURQUOISE.getIndex()); // 填浅蓝色
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
        cellStyle.setWrapText(true);//自动换行
        Font font = book.createFont();
        font.setFontHeightInPoints((short) 11); // 字体大小
        font.setFontName("宋体");
        cellStyle.setFont(font);
        return cellStyle;
    }

    /**
     * 创建内容其它部分单元格样式
     * @param book
     * @return
     */
    public CellStyle createOtherStyle(Workbook book) {
        CellStyle cellStyle = book.createCellStyle();
        cellStyle.setBorderTop(BorderStyle.THIN);//上边框
        cellStyle.setBorderBottom(BorderStyle.THIN);//下边框
        cellStyle.setBorderLeft(BorderStyle.THIN);//左边框
        cellStyle.setBorderRight(BorderStyle.THIN);//右边框
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
        cellStyle.setWrapText(true);//自动换行
        Font font = book.createFont();
        font.setFontHeightInPoints((short) 11); // 字体大小
        font.setFontName("宋体");
        cellStyle.setFont(font);
        return cellStyle;
    }

    /**
     * 设置字符串内注解样式;
     * @param book
     * @param str 传入的待处理字符串;
     * @return
     */
    public RichTextString noteFontStyle(Workbook book, String str) {
        //定义字体
        Font hFont = book.createFont();
        hFont.setFontHeightInPoints((short) 10);//字体大小
        hFont.setFontName("楷体");
        RichTextString richString = null;
        try {//2007版excel
            richString = new XSSFRichTextString(str);//2007版excel
        } catch (Exception e) {
            richString = new HSSFRichTextString(str);//2003版excel
        }
        if (str.indexOf("(") == -1) {
            return richString;
        }
        //字体样式设置到字符串中;
        richString.applyFont(str.indexOf("("), str.indexOf(")"), hFont);
        return richString;
    }

    /**
     * 设置日期格式;
     * @param book
     * @return
     */
    public CellStyle setDateStyle(Workbook book) {
        CellStyle style = book.createCellStyle();
        try {//2007版excel
            DataFormat format = book.createDataFormat();
            style.setDataFormat(format.getFormat("yyyy年MM月dd日"));
        } catch (Exception e) {//2003版excel
            style.setDataFormat(HSSFDataFormat.getBuiltinFormat("yyyy年MM月dd日"));
        }

        return style;
    }

}

六、导入树结构Excel

1.思路如下

  • 导入方法工具类无需任何修改,可直接使用,返回值为 List 对应Excel每一行的数据。

2.代码示例

  • 导入工具类 ExcelImportUtil:调用 readerExcel 方法即可。
  • 入参解读:
  • InputStream inIo : file.getInputStream()
  • String sheetName: Excel第一行的表名
  • int minColumns: 总列数

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.stereotype.Component;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;


/**
 * @description: ExcelImportUtil
 * @author: LYK
 * @create: 2021-05-21 08:09
 **/
public class ExcelImportUtil {
    //excll中存在的样式;
    enum xssfDataType {
        BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER;
    }

    /**
     */
    class MyXSSFSheetHandler extends DefaultHandler {

        /**
         * 表样式
         */
        private StylesTable stylesTable;

        /**
         * 表中独特的字符串
         */
        private ReadOnlySharedStringsTable sharedStringsTable;

        /**
         * 数据输出
         */
        private final PrintStream output;

        /**
         * 最大列数
         */
        private final int minColumnCount;

        // 当看到v start元素时设置
        private boolean vIsOpen;

        // 当单元格开始元素出现时设置;
        //当看到cell close元素时使用。
        private xssfDataType nextDataType;

        // 用于设置数值单元格值的格式
        private short formatIndex;

        private String formatString;

        private final DataFormatter formatter;

        private int thisColumn = -1;

        // 打印到输出流的最后一列
        private int lastColumnNumber = -1;

        // 收集所看到的角色
        private StringBuffer value;

        private String[] record;

        private List<String[]> rows = new ArrayList<String[]>();

        private boolean isCellNull = false;

        /**
         * 接受分析时所需的对象。
         * @param styles
         * @param strings 共享字符串
         * @param cols 要显示的最小列数
         * @param target
         */
        public MyXSSFSheetHandler(StylesTable styles, ReadOnlySharedStringsTable strings, int cols, PrintStream target) {
            this.stylesTable = styles;
            this.sharedStringsTable = strings;
            this.minColumnCount = cols;
            this.output = target;
            this.value = new StringBuffer();
            this.nextDataType = xssfDataType.NUMBER;
            this.formatter = new DataFormatter();
            record = new String[this.minColumnCount];
            rows.clear();// 每次读取都清空行集合
        }

        /*
         * 第一个执行方法,用于设定单元格的数字类型(如日期、数字、字符串等等);
         */
        public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {

            if ("inlineStr".equals(name) || "v".equals(name)) {
                vIsOpen = true;
                // Clear contents cache
                value.setLength(0);
            }
            // c => cell
            else if ("c".equals(name)) {
                // Get the cell reference
                String r = attributes.getValue("r");
                int firstDigit = -1;
                for (int c = 0; c < r.length(); ++c) {
                    if (Character.isDigit(r.charAt(c))) {
                        firstDigit = c;
                        break;
                    }
                }
                thisColumn = nameToColumn(r.substring(0, firstDigit));

                // Set up defaults.
                this.nextDataType = xssfDataType.NUMBER;
                this.formatIndex = -1;
                this.formatString = null;
                String cellType = attributes.getValue("t");
                String cellStyleStr = attributes.getValue("s");
                if ("b".equals(cellType))
                    nextDataType = xssfDataType.BOOL;
                else if ("e".equals(cellType))
                    nextDataType = xssfDataType.ERROR;
                else if ("inlineStr".equals(cellType))
                    nextDataType = xssfDataType.INLINESTR;
                else if ("s".equals(cellType))
                    nextDataType = xssfDataType.SSTINDEX;
                else if ("str".equals(cellType))
                    nextDataType = xssfDataType.FORMULA;
                else if (cellStyleStr != null) {
                    // It's a number, but almost certainly one
                    // with a special style or format
                    int styleIndex = Integer.parseInt(cellStyleStr);
                    XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
                    this.formatIndex = style.getDataFormat();
                    this.formatString = style.getDataFormatString();
                    if (this.formatString == null)
                        this.formatString = BuiltinFormats.getBuiltinFormat(this.formatIndex);
                }
            }

        }

        /*
         * 最后一个执行方法;
         */
        public void endElement(String uri, String localName, String name) throws SAXException {

            String thisStr = null;

            // v => contents of a cell
            if ("v".equals(name)) {
                // 根据需要处理值内容。
                // 这时characters()方法可能会被调用多次
                switch (nextDataType) {

                    case BOOL:
                        char first = value.charAt(0);
                        thisStr = first == '0' ? "FALSE" : "TRUE";
                        break;

                    case ERROR:
                        thisStr = "\"ERROR:" + value.toString() + '"';
                        break;

                    case FORMULA:
                        // A formula could result in a string value,
                        // so always add double-quote characters.
                        thisStr = '"' + value.toString() + '"';
                        break;

                    case INLINESTR:
                        // TODO: have seen an example of this, so it's untested.
                        XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
                        thisStr = '"' + rtsi.toString() + '"';
                        break;
                    //字符串
                    case SSTINDEX:
                        String sstIndex = value.toString();
                        try {
                            int idx = Integer.parseInt(sstIndex);
                            //根据idx索引值获取内容值
                            XSSFRichTextString rtss = new XSSFRichTextString(sharedStringsTable.getEntryAt(idx));
                            thisStr = rtss.toString();
                        } catch (NumberFormatException ex) {
                            output.println("Failed to parse SST index '" + sstIndex + "': " + ex.toString());
                        }
                        break;

                    case NUMBER:
                        String n = value.toString();
                        // 判断是否是日期格式
                        if (HSSFDateUtil.isADateFormat(this.formatIndex, n)) {
                            Double d = Double.parseDouble(n);
                            Date date = HSSFDateUtil.getJavaDate(d);
                            thisStr = formateDateToString(date);
                        } else if (this.formatString != null)
                            thisStr = formatter.formatRawCellContents(Double.parseDouble(n), this.formatIndex,
                                    this.formatString);
                        else
                            thisStr = n;
                        break;

                    default:
                        thisStr = "(TODO: Unexpected type: " + nextDataType + ")";
                        break;
                }

                // Output after we've seen the string contents
                // Emit commas for any fields that were missing on this row
                if (lastColumnNumber == -1) {
                    lastColumnNumber = 0;
                }
                //判断单元格的值是否为空
                if (thisStr == null || "".equals(isCellNull)) {
                    isCellNull = true;// 设置单元格是否为空值
                }
                record[thisColumn] = thisStr;
                // Update column
                if (thisColumn > -1)
                    lastColumnNumber = thisColumn;

            } else if ("row".equals(name)) {

                // Print out any missing commas if needed
                if (minColumns > 0) {
                    // Columns are 0 based
                    if (lastColumnNumber == -1) {
                        lastColumnNumber = 0;
                    }
                    boolean recordflag = record[0] != null;
                    for (int i = 1; i < minColumns; i++) {
                        recordflag = recordflag || (record[i] != null);
                    }
                    if (isCellNull == false && recordflag)// 判断是否空行
                    {
                        rows.add(record.clone());
                        isCellNull = false;
                        for (int i = 0; i < record.length; i++) {
                            record[i] = null;
                        }
                    }
                }
                lastColumnNumber = -1;
            }

        }

        public List<String[]> getRows() {
            return rows;
        }

        public void setRows(List<String[]> rows) {
            this.rows = rows;
        }

        /**
         * 仅在打开适当的元素时捕获字符。最初只是“v”;扩展为inlinestr
         */
        public void characters(char[] ch, int start, int length) throws SAXException {
            if (vIsOpen)
                value.append(ch, start, length);
        }

        /**
         * 将Excel列名(如“C”)转换为从零开始的索引
         *
         * @param name
         * @return 与指定名称对应的索引
         */
        private int nameToColumn(String name) {
            int column = -1;
            for (int i = 0; i < name.length(); ++i) {
                int c = name.charAt(i);
                column = (column + 1) * 26 + c - 'A';
            }
            return column;
        }

        private String formateDateToString(Date date) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd日");//格式化日期
            return sdf.format(date);

        }

    }

    private OPCPackage xlsxPackage;

    private int minColumns;

    private PrintStream output;

    private String sheetName;

    /**
     * 创建新的XLSX->csv转换器
     *
     * @param pkg
     *            要处理的XLSX包
     * @param output
     *            要将csv输出到的打印流
     * @param minColumns
     *            要输出的最小列数,或-1表示无最小值
     */
    public ExcelImportUtil(OPCPackage pkg, PrintStream output, String sheetName, int minColumns) {
        this.xlsxPackage = pkg;
        this.output = output;
        this.minColumns = minColumns;
        this.sheetName = sheetName;
    }

    /**
     *使用指定的样式和共享字符串表解析和显示一个工作表的内容。
     *
     * @param styles
     * @param strings
     * @param sheetInputStream
     */
    public List<String[]> processSheet(StylesTable styles, ReadOnlySharedStringsTable strings,
                                       InputStream sheetInputStream) throws IOException, ParserConfigurationException, SAXException {

        InputSource sheetSource = new InputSource(sheetInputStream);
        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
        SAXParser saxParser = saxFactory.newSAXParser();
        XMLReader sheetParser = saxParser.getXMLReader();
        MyXSSFSheetHandler handler = new MyXSSFSheetHandler(styles, strings, this.minColumns, this.output);
        sheetParser.setContentHandler(handler);
        //解析excel的每条记录,在这个过程中startElement()、characters()、endElement()这三个函数会依次执行
        sheetParser.parse(sheetSource);
        return handler.getRows();
    }

    /**
     * 初始化这个处理程序 将
     *
     * @throws IOException
     * @throws OpenXML4JException
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    public List<String[]> process() throws IOException, OpenXML4JException, ParserConfigurationException, SAXException {

        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);
        XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
        List<String[]> list = null;
        StylesTable styles = xssfReader.getStylesTable();
        XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
        int index = 0;
        while (iter.hasNext()) {
            InputStream stream = iter.next();
            String sheetNameTemp = iter.getSheetName();
            if (this.sheetName.equals(sheetNameTemp)) {
                list = processSheet(styles, strings, stream);
                stream.close();
                ++index;
            }
        }
        return list;
    }

    /**
     * 读取Excel
     *
     * @param inIo
     *            文件流
     * @param sheetName
     *            sheet名称
     * @param minColumns
     *            列总数
     * @return
     * @throws SAXException
     * @throws ParserConfigurationException
     * @throws OpenXML4JException
     * @throws IOException
     */
    public static List<String[]> readerExcel(InputStream inIo, String sheetName, int minColumns) throws IOException,
        OpenXML4JException, ParserConfigurationException, SAXException {
        OPCPackage p = OPCPackage.open(inIo);
        ExcelImportUtil xlsx2csv = new ExcelImportUtil(p, System.out, sheetName, minColumns);
        List<String[]> list = xlsx2csv.process();
        p.close();
        return list;
    }

}
  • 导入方法:
  • 代码研读:
  • sysDeptInfoMap 作用:因为读取Excel为逐行读取,逐行执行保存操作
  • 用下方Excel为例:
  • 第一行,将 ”一栋“ 实体保存在 map key为 LD
  • 第二行,将 ”二栋“ 实体保存在 map key也为 LD 覆盖第一行 LD,将 ”二栋一单元“ 实体保存 map key为 DY ,将 ”一单元1F“ 实体保存 map key为 LC ,因为插入操作子级需要父类实体获取父id 和 祖级列表,此时就可从 map 中根据 对应key 获取父类实体。
  • 第三行 同样执行 保存 map 操作,那么此 map 中的 数据 永远都存在父级数据。
  • Excel基本格式一致可找出各自格式的规律进行解析存储操作。一下仅提供为解析思路。
    java - poi递归导出树结构Excel,导入树结构Excel,树结构递归查询,新增,修改,删除_第6张图片
  @Override
    public void importExcel(MultipartFile file, HttpServletRequest req, HttpServletResponse resp)throws IOException, OpenXML4JException, ParserConfigurationException, SAXException {
        List<String[]> strings = ExcelImportUtil.readerExcel(file.getInputStream(), "项目房间信息", 9);
        //导入项目房间信息所属项目的id
        Integer deptId = 0;
        //此map集合用于保存 父级 对象。
        Map<String,SysDeptInfo> sysDeptInfoMap = new HashMap<>();
        for (int i = 0; i < strings.size() ; i++) {
           if (i>1){
           //数组转换为集合
               List<String> list = Arrays.asList(strings.get(i));
               if (StringUtils.isNotBlank(list.get(0))){
					// 此处主要做校验,判断所属项目存不存在,获取项目id 方便数据插入时表明所属项目
               }
               if (StringUtils.isNotBlank(list.get(1)) && StringUtils.isNotBlank(list.get(2))){
                   //楼栋操作
                   //插入楼栋信息
                   deptInfoMapper.insert(sysDeptInfo);
                   //将楼栋存入Maop
                   sysDeptInfoMap.put("LD",LD);
               }
               if (StringUtils.isNotBlank(list.get(3))){
                   //获取父级楼栋信息
                   SysDeptInfo father = sysDeptInfoMap.get("LD");
                   //单元
                   SysDeptInfo sysDeptInfo = new SysDeptInfo();
                   sysDeptInfo.setParentId(father.getId());
                   sysDeptInfo.setAncestors(father.getAncestors() + "," + sysDeptInfo.getParentId());
                   sysDeptInfo.setLevel(father.getLevel() + 1);
                   //插入单元信息
                   deptInfoMapper.insert(sysDeptInfo);
                   //将单元存入Map
                   sysDeptInfoMap.put("DY",sysDeptInfo);
               }
               if (StringUtils.isNotBlank(list.get(5))){
                   //楼层
                   //获取父级信息
                   SysDeptInfo father = sysDeptInfoMap.get("DY");
                   //楼层
                   SysDeptInfo sysDeptInfo = new SysDeptInfo();
                   sysDeptInfo.setParentId(father.getId());
                   sysDeptInfo.setAncestors(father.getAncestors() + "," + sysDeptInfo.getParentId());
                   //插入楼层信息
                   deptInfoMapper.insert(sysDeptInfo);
                   //将楼层存入Map
                   sysDeptInfoMap.put("LC",sysDeptInfo);
               }
               if (StringUtils.isNotBlank(list.get(7)) && StringUtils.isNotBlank(list.get(8))){
                   //房间
                   //获取父级信息
                   SysDeptInfo father = sysDeptInfoMap.get("LC");
                   //房间
                   SysDeptInfo sysDeptInfo = new SysDeptInfo();
                   sysDeptInfo.setParentId(father.getId());
                   sysDeptInfo.setAncestors(father.getAncestors() + "," + sysDeptInfo.getParentId());
                   sysDeptInfo.setLevel(father.getLevel() + 1);
                   //插入房间信息
                   deptInfoMapper.insert(sysDeptInfo);
               }
           }
        }
        sysDeptInfoMap.clear();
    }

吹牛逼结束,感谢观看


你可能感兴趣的:(java,poi,excel,递归算法,递归法)