Java Excel導入導出,基於XML和Easy-excel使用

1.前言

  • 在工做時,遇到過這樣的需求,須要靈活的對工單進行導入或導出,之前本身也作過,但使用不靈活繁瑣。我想能不能像配置文件同樣可配置的導入導出,那樣使用起來就方便許多。java

2.SpringMVC項目搭建

  • 建立基於Maven版本管理Springmvc項目,如下是主要的依賴,用jetty做爲內嵌的服務器,直接啓動。mysql

  • 架包相關依賴pom.xml。git

<!-- 版本定義 -->
    <properties>
        <commons-beanutils.version>1.9.2</commons-beanutils.version>
        <commons-collections.version>3.2.1</commons-collections.version>
        <commons-lang.version>2.6</commons-lang.version>
        <poi.version>3.14</poi.version>
        <xmlbeans.version>2.6.0</xmlbeans.version>
        <spring.version>4.2.6.RELEASE</spring.version>
        <log4j.version>1.2.17</log4j.version>
        <druid.version>1.0.11</druid.version>
    </properties>

    <!-- <dependencyManagement> -->
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- commons -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${commons-beanutils.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>${commons-collections.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>${commons-lang.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>


        <!-- POI -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>${poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>${poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>${poi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlbeans</groupId>
            <artifactId>xmlbeans</artifactId>
            <version>${xmlbeans.version}</version>
        </dependency>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- log -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <!--mybatis start-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <!--mybatis end-->

        <!--spring dao 依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--spring web依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--spring web依賴 end-->


        <!--spring 其餘 依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--Severlet Web 相關依賴-->
        <!--2個標籤庫-->
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.6.5</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- 日誌
       java 日誌:slf4j 是規範、接口
       log4j\logback\common-logging 是實現
       -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.15</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.3</version>
        </dependency>
        <!-- 實現了slf4j 接口並整合 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.3</version>
        </dependency>

        <!--數據庫層依賴-->
        <!-- connection pool start-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!-- connection pool end-->

        <!-- Jetty -->
        <dependency>
            <groupId>org.eclipse.jetty.aggregate</groupId>
            <artifactId>jetty-all</artifactId>
            <version>8.1.16.v20140903</version>
            <scope>test</scope>
        </dependency>
        <!-- Jetty Webapp -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-webapp</artifactId>
            <version>8.1.16.v20140903</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-jsp</artifactId>
            <version>8.1.16.v20140903</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.8</version>
        </dependency>

        <!-- json插件 -->
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.13</version>
        </dependency>
        <!-- 文件上傳 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.2.2</version>
        </dependency>
        <!--common start-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>


        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>3.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-mongodb</artifactId>
            <version>1.8.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>1.11.4.RELEASE</version>
        </dependency>

    </dependencies>
  • 其它相關配置沒啥特別,詳見源碼web

3.Xml配置和Easy-excel使用

  • 下面是員工信息模型。spring

//員工模型
    public class EmployeeModel {

    /**
     * ID
     */
    protected Integer id;
    /**
     * 建立時間
     */
    protected Date createTime;
    /**
     * 姓名
     */
    private String name;
    /**
     * 性別 0:男 1:女
     */
    private Integer sex;
    /**
     * 年齡
     */
    private Integer age;
    /**
     * 手機號
     */
    private String mobile;
    /**
     * 狀態 0:在職
     *     1:離職
     */
    private Integer status;
    //省略getter setter
    
    }
  • 有以下的Excel文件格式,須要映射成學生實體類。sql

圖片描述

  • 那麼使用easy-excel 如何導入呢?mongodb

  • 前一行是屬於不規則的數據,從標題行之後纔是規則的數據,也就是從規則數據以後才能正常轉換成JavaBean。數據庫

  • 具體實現代碼,首先編寫配置文件:excel-config.xml。express

<excels> 
    <excel id="employee" class="org.easy.web.ExcelModel.EmployeeModel" sheetname="員工信息列表" defaultColumnWidth="5000" enableStyle="true">
        <field name="id" title="ID" align="center" isNull="true" uniformStyle="true" columnWidth="5000" />
        <field name="createTime" title="入職時間" pattern="yyyy/MM/dd" isNull="true" uniformStyle="true" columnWidth="5000" />
        <field name="name" title="姓名" align="center" isNull="true" uniformStyle="true" columnWidth="5000" />
        <field name="sex" title="性別" align="center" isNull="true" uniformStyle="true" columnWidth="5000" format="1:女,0:男"/>
        <field name="age" title="年齡" align="center" isNull="true" uniformStyle="true" columnWidth="5000" regex="^[1-9]\d*$" regexErrMsg="必須是數字"/>
        <field name="mobile" title="手機" align="center" isNull="true" uniformStyle="true" columnWidth="5000" regex="^[1][3,4,5,8][0-9]{9}$" regexErrMsg="格式錯誤"/>
        <field name="status" title="在職狀態" align="center" isNull="true" uniformStyle="true" columnWidth="5000" format="1:離職,0:在職" />
    </excel>
    </excels>
  • 解釋,每一個excel表示一種Excel文件到JavaBean的映射規則,該規則能夠是導入和導出。apache

  • 配置了一個id爲employee的映射,要映射對應的JavaBean實體爲 EmployeeModel。

<excel id="employee" class="org.easy.web.ExcelModel.EmployeeModel">
  • excel文件中標題爲ID的列,把它的值映射到 EmployeeModel.id屬性上。

<field name="id" title="ID"/>
  • isNull屬性,表示在excel文件中標題爲【年齡】的單元格的值不能爲空,而且符合正則【 regex】,當正則校驗沒有經過時,會提示【xxx行,[ 年齡 ]必須是數字,若是爲空同理,xxx行[年齡]不能爲空】。

<field name="age" title="年齡" isNull="false" regex="^[1-9]\d*$" regexErrMsg="必須是數字"/>
  • pattern:不作過多解釋,SimpleDateFormat(pattern),導入數據時字符串的值如何轉換成日期。支持多種映射pattern,使用【英文逗號】進行分割。

<field name="createTime" title="入職時間" pattern="yyyy/MM/dd" isNull="true" uniformStyle="true" columnWidth="5000" />
  • format屬性,觀察上面的excel文件結構會發現狀態列是中文,那麼導入時,可能javaBean中並非中文,而是數字或其餘,那麼如何把它轉換成數字呢?format就是作這個事情的。導入時它會以【,分割:前面的做爲導入時使用的值,:後面的做爲導出時使用的值】:後面值進行逆推,導出時同理。思考一個問題?若是這個值不肯定如何解決,或者這個值須要到數據庫校驗?好比是個城市地址,這個時候是須要查詢數據庫進行比對的,若是地址不存在則拋出錯誤提示信息的,就說這麼多,easy-excel已經作好了,支持自定義的轉換器能夠解決。

<field name="status" title="在職狀態" align="center" isNull="true" uniformStyle="true" columnWidth="5000" format="1:離職,0:在職" />
  • 那麼核心的代碼是哪幾行?

//導入
    public Result importExcel(@RequestParam(value = "file", required = true) MultipartFile file) throws Exception {
        Boolean excel = StringUtil.isExcel(file.getOriginalFilename());
        if (!excel){
            return Result.wrapErrorResult("該文件不是Excel格式");
        }
        InputStream fis = file.getInputStream();
        ExcelContext context = new ExcelContext("excel/config.xml");
        ExcelUtil<EmployeeModel> excelUtil = new ExcelUtil(excelContext, "employee");
        List<EmployeeModel> stus;
        try {
            stus = excelUtil.importExcel(1, fis);
        } catch (ExcelException e) {//這裏主動返回錯誤信息
            return Result.wrapErrorResult(e.getMessage());
        }
        List<Employee> list=BdUtil.e2OList(stus,Employee.class);
        return Result.wrapSuccessfulResult(list);
    }

    //導出
    public void exportExcel(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ExcelUtil<EmployeeModel> excelUtil = new ExcelUtil(excelContext, "employee");
        final List<EmployeeModel> list = getEmployeeList();
        List<String> specifyFields = new ArrayList<String>(
        ExcelHeader header=new ExcelHeader() {
            public void buildHeader(Sheet sheet, ExcelDefinition excelDefinition, List<?> beans,Workbook workbook) {
                // 設置第一行樣式
                CellStyle style1 = workbook.createCellStyle();
                // 水平居中
                style1.setAlignment(HSSFCellStyle.ALIGN_CENTER);
                // 垂直居中
                style1.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
                // 設置字體 大小
                Font font1 = workbook.createFont();
                font1.setFontHeightInPoints((short) 18);
                style1.setFont(font1);

                //第一行數據
                Row row1 = sheet.createRow(0);
                Cell cell1 = row1.createCell(0);
                cell1.setCellValue("共導出【"+list.size()+"】條數據");
                // 行高
                row1.setHeight((short) 700);
                cell1.setCellStyle(style1);

                //合併單元格
                CellRangeAddress region = new CellRangeAddress(0, 0, 0, 2);
                sheet.addMergedRegion(region);
            }
        };
        excelUtil.exportExcel(response,list, header,specifyFields,"員工信息");
    }
  • 只有準備數據、建立上下文、讀取excel。。一般在真實的常見建立上下文均可以省略了,由於它會交給spring容器管理,整個jvm中,只保持一個實例就夠了。

  • 關於導入配置的一個很重要的屬性:resolveFieldValueConverterName。

  • 它是一個ResolveFieldValueConverter接口的實現類:假設咱們的Excel中有地址,或者結合業務中多是其餘,都是須要查詢數據庫,或者通過更復雜的業務邏輯進行校驗的,那麼咱們能夠配置它。咱們來觀察這個接口作了什麼事?

/**
     * 解析Excel值解析接口
     *
     */

    public interface ResolveFieldValueConverter {
    
    /**
     * 操做類型,導入或導出
     */
    enum Type {
        EXPORT, IMPORT
    }
    
    /**
     * 解析配置中Field元素 處理後的值
     * @param bean Excel配置的JavaBean對象
     * @param value Excel原值
     * @param fieldValue FieldValue信息
     * @param type 導入或導出
     * @param rowNum 行號
     * @return 解析結果對應的value
     * @throws Exception
     */
    public Object resolveFieldValue(Object bean,Object value, FieldValue fieldValue, Type type,int rowNum) throws Exception;
    }
  • 核心只有一個方法resolveFieldValue。

  • 咱們能夠自定義它的實現類,而後把全類名註冊到 resolveFieldValueConverterName
    屬性上如:假設建立人是須要查詢用戶表進行校驗,若是沒有則不容許導入,咱們則能夠在自定義的實現類拋出一個異常,能夠精準的提示用戶多少行,哪個字段【標題】的值錯誤了。

  • 直接上代碼,下面是導入導出封裝好工具類。

/**
     * Excel 導出方法調用(主)
     */
    public class ExcelUtil<T> {

    //建立excel上下文實例,配置文件路徑
    private ExcelContext context;
    //Excel配置文件中配置的id
    private String excelId;

    public ExcelUtil(ExcelContext context, String excelId) {
        this.context = context;
        this.excelId = excelId;
    }
    
    /**
     * @param startRow 頭部從第幾行開始導入
     * @param fis      文件流
     * @return
     * @throws Exception
     */
    public List<T> importExcel(int startRow, InputStream fis) throws Exception {
        //第二個參數須要注意,它是指標題索引的位置,可能你的前幾行並非標題,而是其餘信息,
        //好比數據批次號之類的,關於如何轉換成javaBean,具體參考配置信息描述
        ExcelImportResult result = context.readExcel(excelId, startRow, fis);
    //        System.out.println(result.getHeader());
        List<T> stus = result.getListBean();
        return stus;
        //這種方式和上面的沒有任何區別,底層方法默認標題索引爲0
        //context.readExcel(excelId, fis);
    }
    
        /**
         * 導出Excel並下載
         * @param response
         * @param list     結果集,null默認導出模板
         * @param header   自定義表頭,null默認無
         * @param specifyFields 導出字段,null導出全部字段
         * @param fileName  下載的文件名
         */
        public void exportExcel(HttpServletResponse response, List<T> list,ExcelHeader header, List<String> specifyFields, String fileName) {
            File file = null;
            OutputStream ops = null;
            OutputStream out = null;
            Workbook workbook = null;
            try {
                /**
                 * Step1:建立臨時xlsx文件
                 */
                file = File.createTempFile("tmp", ".xlsx");
                /**
                 * Step2:導出數據寫入臨時xlsx文件
                 */
                String path = file.getAbsolutePath();
                ops = new FileOutputStream(path);
                //獲取POI建立結果
                if (list != null && !list.isEmpty()) {
                    workbook = context.createExcel(excelId, list, header, specifyFields);
    //                workbook = context.createExcel(excelId, list);
                } else {//查詢結果爲空,導出模板
                    workbook = context.createExcelTemplate(excelId, header, specifyFields);
                }
                workbook.write(ops);
                /**
                 * Step3:下載臨時xlsx文件
                 */
                response.reset();
                response.setContentType("application/octet-stream; charset=utf-8");
                fileName = URLEncoder.encode(fileName + ".xlsx", "UTF-8");
                response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
                out = response.getOutputStream();
                out.write(FileUtils.readFileToByteArray(file));
                out.flush();
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                file.deleteOnExit();//臨時文件若存在,則刪除
                try {
                    if (ops != null) {
                        ops.close();
                    }
                    if (workbook != null) {
                        workbook.close();
                    }
                    if (out != null) {
                        out.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
     }
相關文章
相關標籤/搜索