导入导出Excel工具类ExcelUtil

前言

 

前段时间做的分布式集成平台项目中,许多模块都用到了导入导出Excel的功能,于是决定封装一个ExcelUtil类,专门用来处理Excel的导入和导出

 

本项目的持久化层用的是JPA(底层用hibernate实现),所以导入和导出也都是基于实体类的。

 

在编写ExcelUtil之前,在网上查了一些资料。Java中用来处理Excel的第三方开源项目主要就是POIJXLpoi功能强大,但是比较耗资源,对于大数据量的导入导出性能不是太好;jxl功能简单,但是性能比较好。

 

由于本项目的导入导出更多关注性能问题,而且jxl提供的功能基本也都够用了,于是选择了jxl作为支持。

 



实战

 

导出就是将List转化为ExcellistToExcel

导入就是将Excel转化为ListexcelToList

 

导入导出中会出现各种各样的问题,比如:数据源为空、有重复行等,我自定义了一个ExcelException异常类,用来处理这些问题。

 

ExcelException

 

[java] view plain copy 在CODE上查看代码片
  1. package common.tool.excel;  
  2.   
  3. public class ExcelException extends Exception {  
  4.   
  5.     public ExcelException() {  
  6.         // TODO Auto-generated constructor stub  
  7.     }  
  8.   
  9.     public ExcelException(String message) {  
  10.         super(message);  
  11.         // TODO Auto-generated constructor stub  
  12.     }  
  13.   
  14.     public ExcelException(Throwable cause) {  
  15.         super(cause);  
  16.         // TODO Auto-generated constructor stub  
  17.     }  
  18.   
  19.     public ExcelException(String message, Throwable cause) {  
  20.         super(message, cause);  
  21.         // TODO Auto-generated constructor stub  
  22.     }  
  23.   
  24.   
  25. }  


下面就是该文的主角ExcelUtil登场了,作为一个工具类,其内的所有方法都是静态的,方便使用。


ExcelUitl类

 

[java] view plain copy 在CODE上查看代码片
  1. /** 
  2.  * @author     : WH 
  3.  * @group      : tgb8 
  4.  * @Date       : 2014-1-2 下午9:13:21 
  5.  * @Comments   : 导入导出Excel工具类 
  6.  * @Version    : 1.0.0 
  7.  */  
  8.   
  9. public class ExcelUtil  {  
  10.       
  11.     /** 
  12.      * @MethodName  : listToExcel 
  13.      * @Description : 导出Excel(可以导出到本地文件系统,也可以导出到浏览器,可自定义工作表大小) 
  14.      * @param list      数据源 
  15.      * @param fieldMap      类的英文属性和Excel中的中文列名的对应关系 
  16.      * 如果需要的是引用对象的属性,则英文属性使用类似于EL表达式的格式 
  17.      * 如:list中存放的都是student,student中又有college属性,而我们需要学院名称,则可以这样写 
  18.      * fieldMap.put("college.collegeName","学院名称") 
  19.      * @param sheetName 工作表的名称 
  20.      * @param sheetSize 每个工作表中记录的最大个数 
  21.      * @param out       导出流 
  22.      * @throws ExcelException 
  23.      */  
  24.     public static   void   listToExcel (  
  25.             List list ,  
  26.             LinkedHashMap fieldMap,  
  27.             String sheetName,  
  28.             int sheetSize,  
  29.             OutputStream out  
  30.             ) throws ExcelException{  
  31.           
  32.           
  33.         if(list.size()==0 || list==null){  
  34.             throw new ExcelException("数据源中没有任何数据");  
  35.         }  
  36.           
  37.         if(sheetSize>65535 || sheetSize<1){  
  38.             sheetSize=65535;  
  39.         }  
  40.           
  41.         //创建工作簿并发送到OutputStream指定的地方  
  42.         WritableWorkbook wwb;  
  43.         try {  
  44.             wwb = Workbook.createWorkbook(out);  
  45.               
  46.             //因为2003的Excel一个工作表最多可以有65536条记录,除去列头剩下65535条  
  47.             //所以如果记录太多,需要放到多个工作表中,其实就是个分页的过程  
  48.             //1.计算一共有多少个工作表  
  49.             double sheetNum=Math.ceil(list.size()/new Integer(sheetSize).doubleValue());  
  50.               
  51.             //2.创建相应的工作表,并向其中填充数据  
  52.             for(int i=0; i
  53.                 //如果只有一个工作表的情况  
  54.                 if(1==sheetNum){  
  55.                     WritableSheet sheet=wwb.createSheet(sheetName, i);  
  56.                     fillSheet(sheet, list, fieldMap, 0, list.size()-1);  
  57.                   
  58.                 //有多个工作表的情况  
  59.                 }else{  
  60.                     WritableSheet sheet=wwb.createSheet(sheetName+(i+1), i);  
  61.                       
  62.                     //获取开始索引和结束索引  
  63.                     int firstIndex=i*sheetSize;  
  64.                     int lastIndex=(i+1)*sheetSize-1>list.size()-1 ? list.size()-1 : (i+1)*sheetSize-1;  
  65.                     //填充工作表  
  66.                     fillSheet(sheet, list, fieldMap, firstIndex, lastIndex);  
  67.                 }  
  68.             }  
  69.               
  70.             wwb.write();  
  71.             wwb.close();  
  72.           
  73.         }catch (Exception e) {  
  74.             e.printStackTrace();  
  75.             //如果是ExcelException,则直接抛出  
  76.             if(e instanceof ExcelException){  
  77.                 throw (ExcelException)e;  
  78.               
  79.             //否则将其它异常包装成ExcelException再抛出  
  80.             }else{  
  81.                 throw new ExcelException("导出Excel失败");  
  82.             }  
  83.         }  
  84.               
  85.     }  
  86.       
  87.     /** 
  88.      * @MethodName  : listToExcel 
  89.      * @Description : 导出Excel(可以导出到本地文件系统,也可以导出到浏览器,工作表大小为2003支持的最大值) 
  90.      * @param list      数据源 
  91.      * @param fieldMap      类的英文属性和Excel中的中文列名的对应关系 
  92.      * @param out       导出流 
  93.      * @throws ExcelException 
  94.      */  
  95.     public static    void   listToExcel (  
  96.             List list ,  
  97.             LinkedHashMap fieldMap,  
  98.             String sheetName,  
  99.             OutputStream out  
  100.             ) throws ExcelException{  
  101.           
  102.         listToExcel(list, fieldMap, sheetName, 65535, out);  
  103.           
  104.     }  
  105.       
  106.       
  107.     /** 
  108.      * @MethodName  : listToExcel 
  109.      * @Description : 导出Excel(导出到浏览器,可以自定义工作表的大小) 
  110.      * @param list      数据源 
  111.      * @param fieldMap      类的英文属性和Excel中的中文列名的对应关系 
  112.      * @param sheetSize    每个工作表中记录的最大个数 
  113.      * @param response  使用response可以导出到浏览器 
  114.      * @throws ExcelException 
  115.      */  
  116.     public static    void   listToExcel (  
  117.             List list ,  
  118.             LinkedHashMap fieldMap,  
  119.             String sheetName,  
  120.             int sheetSize,  
  121.             HttpServletResponse response   
  122.             ) throws ExcelException{  
  123.           
  124.         //设置默认文件名为当前时间:年月日时分秒  
  125.         String fileName=new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()).toString();  
  126.           
  127.         //设置response头信息  
  128.         response.reset();            
  129.         response.setContentType("application/vnd.ms-excel");        //改成输出excel文件  
  130.         response.setHeader("Content-disposition","attachment; filename="+fileName+".xls" );  
  131.   
  132.         //创建工作簿并发送到浏览器  
  133.         try {  
  134.               
  135.             OutputStream out=response.getOutputStream();  
  136.             listToExcel(list, fieldMap, sheetName, sheetSize,out );  
  137.               
  138.         } catch (Exception e) {  
  139.             e.printStackTrace();  
  140.               
  141.             //如果是ExcelException,则直接抛出  
  142.             if(e instanceof ExcelException){  
  143.                 throw (ExcelException)e;  
  144.               
  145.             //否则将其它异常包装成ExcelException再抛出  
  146.             }else{  
  147.                 throw new ExcelException("导出Excel失败");  
  148.             }  
  149.         }  
  150.     }  
  151.       
  152.       
  153.     /** 
  154.      * @MethodName  : listToExcel 
  155.      * @Description : 导出Excel(导出到浏览器,工作表的大小是2003支持的最大值) 
  156.      * @param list      数据源 
  157.      * @param fieldMap      类的英文属性和Excel中的中文列名的对应关系 
  158.      * @param response  使用response可以导出到浏览器 
  159.      * @throws ExcelException 
  160.      */  
  161.     public static   void   listToExcel (  
  162.             List list ,  
  163.             LinkedHashMap fieldMap,  
  164.             String sheetName,  
  165.             HttpServletResponse response   
  166.             ) throws ExcelException{  
  167.           
  168.         listToExcel(list, fieldMap, sheetName, 65535, response);  
  169.     }  
  170.       
  171.     /** 
  172.      * @MethodName          : excelToList 
  173.      * @Description             : 将Excel转化为List 
  174.      * @param in                    :承载着Excel的输入流 
  175.      * @param sheetIndex        :要导入的工作表序号 
  176.      * @param entityClass       :List中对象的类型(Excel中的每一行都要转化为该类型的对象) 
  177.      * @param fieldMap          :Excel中的中文列头和类的英文属性的对应关系Map 
  178.      * @param uniqueFields  :指定业务主键组合(即复合主键),这些列的组合不能重复 
  179.      * @return                      :List 
  180.      * @throws ExcelException 
  181.      */  
  182.     public static   List  excelToList(  
  183.             InputStream in,  
  184.             String sheetName,  
  185.             Class entityClass,  
  186.             LinkedHashMap fieldMap,  
  187.             String[] uniqueFields  
  188.             ) throws ExcelException{  
  189.           
  190.         //定义要返回的list  
  191.         List resultList=new ArrayList();  
  192.           
  193.         try {  
  194.               
  195.             //根据Excel数据源创建WorkBook  
  196.             Workbook wb=Workbook.getWorkbook(in);  
  197.             //获取工作表  
  198.             Sheet sheet=wb.getSheet(sheetName);  
  199.               
  200.             //获取工作表的有效行数  
  201.             int realRows=0;  
  202.             for(int i=0;i
  203.                   
  204.                 int nullCols=0;  
  205.                 for(int j=0;j
  206.                     Cell currentCell=sheet.getCell(j,i);  
  207.                     if(currentCell==null || "".equals(currentCell.getContents().toString())){  
  208.                         nullCols++;  
  209.                     }  
  210.                 }  
  211.                   
  212.                 if(nullCols==sheet.getColumns()){  
  213.                     break;  
  214.                 }else{  
  215.                     realRows++;  
  216.                 }  
  217.             }  
  218.               
  219.               
  220.             //如果Excel中没有数据则提示错误  
  221.             if(realRows<=1){  
  222.                 throw new ExcelException("Excel文件中没有任何数据");  
  223.             }  
  224.               
  225.               
  226.             Cell[] firstRow=sheet.getRow(0);  
  227.   
  228.             String[] excelFieldNames=new String[firstRow.length];  
  229.               
  230.             //获取Excel中的列名  
  231.             for(int i=0;i
  232.                 excelFieldNames[i]=firstRow[i].getContents().toString().trim();  
  233.             }  
  234.               
  235.             //判断需要的字段在Excel中是否都存在  
  236.             boolean isExist=true;  
  237.             List excelFieldList=Arrays.asList(excelFieldNames);  
  238.             for(String cnName : fieldMap.keySet()){  
  239.                 if(!excelFieldList.contains(cnName)){  
  240.                     isExist=false;  
  241.                     break;  
  242.                 }  
  243.             }  
  244.               
  245.             //如果有列名不存在,则抛出异常,提示错误  
  246.             if(!isExist){  
  247.                 throw new ExcelException("Excel中缺少必要的字段,或字段名称有误");  
  248.             }  
  249.               
  250.               
  251.             //将列名和列号放入Map中,这样通过列名就可以拿到列号  
  252.             LinkedHashMap colMap=new LinkedHashMap();  
  253.             for(int i=0;i
  254.                 colMap.put(excelFieldNames[i], firstRow[i].getColumn());  
  255.             }   
  256.               
  257.               
  258.               
  259.             //判断是否有重复行  
  260.             //1.获取uniqueFields指定的列  
  261.             Cell[][] uniqueCells=new Cell[uniqueFields.length][];  
  262.             for(int i=0;i
  263.                 int col=colMap.get(uniqueFields[i]);  
  264.                 uniqueCells[i]=sheet.getColumn(col);  
  265.             }  
  266.               
  267.             //2.从指定列中寻找重复行  
  268.             for(int i=1;i
  269.                 int nullCols=0;  
  270.                 for(int j=0;j
  271.                     String currentContent=uniqueCells[j][i].getContents();  
  272.                     Cell sameCell=sheet.findCell(currentContent,   
  273.                             uniqueCells[j][i].getColumn(),  
  274.                             uniqueCells[j][i].getRow()+1,   
  275.                             uniqueCells[j][i].getColumn(),   
  276.                             uniqueCells[j][realRows-1].getRow(),   
  277.                             true);  
  278.                     if(sameCell!=null){  
  279.                         nullCols++;  
  280.                     }  
  281.                 }  
  282.                   
  283.                 if(nullCols==uniqueFields.length){  
  284.                     throw new ExcelException("Excel中有重复行,请检查");  
  285.                 }  
  286.             }  
  287.   
  288.             //将sheet转换为list  
  289.             for(int i=1;i
  290.                 //新建要转换的对象  
  291.                 T entity=entityClass.newInstance();  
  292.                   
  293.                 //给对象中的字段赋值  
  294.                 for(Entry entry : fieldMap.entrySet()){  
  295.                     //获取中文字段名  
  296.                     String cnNormalName=entry.getKey();  
  297.                     //获取英文字段名  
  298.                     String enNormalName=entry.getValue();  
  299.                     //根据中文字段名获取列号  
  300.                     int col=colMap.get(cnNormalName);  
  301.                       
  302.                     //获取当前单元格中的内容  
  303.                     String content=sheet.getCell(col, i).getContents().toString().trim();  
  304.                       
  305.                     //给对象赋值  
  306.                     setFieldValueByName(enNormalName, content, entity);  
  307.                 }  
  308.                   
  309.                 resultList.add(entity);  
  310.             }  
  311.         } catch(Exception e){  
  312.             e.printStackTrace();  
  313.             //如果是ExcelException,则直接抛出  
  314.             if(e instanceof ExcelException){  
  315.                 throw (ExcelException)e;  
  316.               
  317.             //否则将其它异常包装成ExcelException再抛出  
  318.             }else{  
  319.                 e.printStackTrace();  
  320.                 throw new ExcelException("导入Excel失败");  
  321.             }  
  322.         }  
  323.         return resultList;  
  324.     }  
  325.       
  326.       
  327.       
  328.       
  329.       
  330.     /*<-------------------------辅助的私有方法----------------------------------------------->*/  
  331.     /** 
  332.      * @MethodName  : getFieldValueByName 
  333.      * @Description : 根据字段名获取字段值 
  334.      * @param fieldName 字段名 
  335.      * @param o 对象 
  336.      * @return  字段值 
  337.      */  
  338.     private static  Object getFieldValueByName(String fieldName, Object o) throws Exception{  
  339.           
  340.         Object value=null;  
  341.         Field field=getFieldByName(fieldName, o.getClass());  
  342.           
  343.         if(field !=null){  
  344.             field.setAccessible(true);  
  345.             value=field.get(o);  
  346.         }else{  
  347.             throw new ExcelException(o.getClass().getSimpleName() + "类不存在字段名 "+fieldName);  
  348.         }  
  349.           
  350.         return value;  
  351.     }  
  352.       
  353.     /** 
  354.      * @MethodName  : getFieldByName 
  355.      * @Description : 根据字段名获取字段 
  356.      * @param fieldName 字段名 
  357.      * @param clazz 包含该字段的类 
  358.      * @return 字段 
  359.      */  
  360.     private static Field getFieldByName(String fieldName, Class  clazz){  
  361.         //拿到本类的所有字段  
  362.         Field[] selfFields=clazz.getDeclaredFields();  
  363.           
  364.         //如果本类中存在该字段,则返回  
  365.         for(Field field : selfFields){  
  366.             if(field.getName().equals(fieldName)){  
  367.                 return field;  
  368.             }  
  369.         }  
  370.           
  371.         //否则,查看父类中是否存在此字段,如果有则返回  
  372.         Class superClazz=clazz.getSuperclass();  
  373.         if(superClazz!=null  &&  superClazz !=Object.class){  
  374.             return getFieldByName(fieldName, superClazz);  
  375.         }  
  376.           
  377.         //如果本类和父类都没有,则返回空  
  378.         return null;  
  379.     }  
  380.       
  381.       
  382.       
  383.     /** 
  384.      * @MethodName  : getFieldValueByNameSequence 
  385.      * @Description :  
  386.      * 根据带路径或不带路径的属性名获取属性值 
  387.      * 即接受简单属性名,如userName等,又接受带路径的属性名,如student.department.name等 
  388.      *  
  389.      * @param fieldNameSequence  带路径的属性名或简单属性名 
  390.      * @param o 对象 
  391.      * @return  属性值 
  392.      * @throws Exception 
  393.      */  
  394.     private static  Object getFieldValueByNameSequence(String fieldNameSequence, Object o) throws Exception{  
  395.           
  396.         Object value=null;  
  397.           
  398.         //将fieldNameSequence进行拆分  
  399.         String[] attributes=fieldNameSequence.split("\\.");  
  400.         if(attributes.length==1){  
  401.             value=getFieldValueByName(fieldNameSequence, o);  
  402.         }else{  
  403.             //根据属性名获取属性对象  
  404.             Object fieldObj=getFieldValueByName(attributes[0], o);  
  405.             String subFieldNameSequence=fieldNameSequence.substring(fieldNameSequence.indexOf(".")+1);  
  406.             value=getFieldValueByNameSequence(subFieldNameSequence, fieldObj);  
  407.         }  
  408.         return value;   
  409.           
  410.     }   
  411.       
  412.       
  413.     /** 
  414.      * @MethodName  : setFieldValueByName 
  415.      * @Description : 根据字段名给对象的字段赋值 
  416.      * @param fieldName  字段名 
  417.      * @param fieldValue    字段值 
  418.      * @param o 对象 
  419.      */  
  420.     private static void setFieldValueByName(String fieldName,Object fieldValue,Object o) throws Exception{  
  421.           
  422.             Field field=getFieldByName(fieldName, o.getClass());  
  423.             if(field!=null){  
  424.                 field.setAccessible(true);  
  425.                 //获取字段类型  
  426.                 Class fieldType = field.getType();    
  427.                   
  428.                 //根据字段类型给字段赋值  
  429.                 if (String.class == fieldType) {    
  430.                     field.set(o, String.valueOf(fieldValue));    
  431.                 } else if ((Integer.TYPE == fieldType)    
  432.                         || (Integer.class == fieldType)) {    
  433.                     field.set(o, Integer.parseInt(fieldValue.toString()));    
  434.                 } else if ((Long.TYPE == fieldType)    
  435.                         || (Long.class == fieldType)) {    
  436.                     field.set(o, Long.valueOf(fieldValue.toString()));    
  437.                 } else if ((Float.TYPE == fieldType)    
  438.                         || (Float.class == fieldType)) {    
  439.                     field.set(o, Float.valueOf(fieldValue.toString()));    
  440.                 } else if ((Short.TYPE == fieldType)    
  441.                         || (Short.class == fieldType)) {    
  442.                     field.set(o, Short.valueOf(fieldValue.toString()));    
  443.                 } else if ((Double.TYPE == fieldType)    
  444.                         || (Double.class == fieldType)) {    
  445.                     field.set(o, Double.valueOf(fieldValue.toString()));    
  446.                 } else if (Character.TYPE == fieldType) {    
  447.                     if ((fieldValue!= null) && (fieldValue.toString().length() > 0)) {    
  448.                         field.set(o, Character    
  449.                                 .valueOf(fieldValue.toString().charAt(0)));    
  450.                     }    
  451.                 }else if(Date.class==fieldType){  
  452.                     field.set(o, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(fieldValue.toString()));  
  453.                 }else{  
  454.                     field.set(o, fieldValue);  
  455.                 }  
  456.             }else{  
  457.                 throw new ExcelException(o.getClass().getSimpleName() + "类不存在字段名 "+fieldName);  
  458.             }  
  459.     }  
  460.       
  461.       
  462.     /** 
  463.      * @MethodName  : setColumnAutoSize 
  464.      * @Description : 设置工作表自动列宽和首行加粗 
  465.      * @param ws 
  466.      */  
  467.     private static void setColumnAutoSize(WritableSheet ws,int extraWith){  
  468.         //获取本列的最宽单元格的宽度  
  469.         for(int i=0;i
  470.             int colWith=0;  
  471.             for(int j=0;j
  472.                 String content=ws.getCell(i,j).getContents().toString();  
  473.                 int cellWith=content.length();  
  474.                 if(colWith
  475.                     colWith=cellWith;  
  476.                 }  
  477.             }  
  478.             //设置单元格的宽度为最宽宽度+额外宽度  
  479.             ws.setColumnView(i, colWith+extraWith);  
  480.         }  
  481.           
  482.     }  
  483.       
  484.     /** 
  485.      * @MethodName  : fillSheet 
  486.      * @Description : 向工作表中填充数据 
  487.      * @param sheet     工作表  
  488.      * @param list  数据源 
  489.      * @param fieldMap 中英文字段对应关系的Map 
  490.      * @param firstIndex    开始索引 
  491.      * @param lastIndex 结束索引 
  492.      */  
  493.     private static  void fillSheet(  
  494.             WritableSheet sheet,  
  495.             List list,  
  496.             LinkedHashMap fieldMap,  
  497.             int firstIndex,  
  498.             int lastIndex  
  499.             )throws Exception{  
  500.           
  501.         //定义存放英文字段名和中文字段名的数组  
  502.         String[] enFields=new String[fieldMap.size()];  
  503.         String[] cnFields=new String[fieldMap.size()];  
  504.           
  505.         //填充数组  
  506.         int count=0;  
  507.         for(Entry entry:fieldMap.entrySet()){  
  508.             enFields[count]=entry.getKey();  
  509.             cnFields[count]=entry.getValue();  
  510.             count++;  
  511.         }  
  512.         //填充表头  
  513.         for(int i=0;i
  514.             Label label=new Label(i,0,cnFields[i]);  
  515.             sheet.addCell(label);  
  516.         }  
  517.           
  518.         //填充内容  
  519.         int rowNo=1;  
  520.         for(int index=firstIndex;index<=lastIndex;index++){  
  521.             //获取单个对象  
  522.             T item=list.get(index);  
  523.             for(int i=0;i
  524.                 Object objValue=getFieldValueByNameSequence(enFields[i], item);  
  525.                 String fieldValue=objValue==null ? "" : objValue.toString();  
  526.                 Label label =new Label(i,rowNo,fieldValue);  
  527.                 sheet.addCell(label);  
  528.             }  
  529.               
  530.             rowNo++;  
  531.         }  
  532.           
  533.         //设置自动列宽  
  534.         setColumnAutoSize(sheet, 5);  
  535.     }  
  536.       
  537. }  


该工具类有4个重载的导出方法和1个导入方法,大家可以根据实际情况进行选择。

 

总结

导入和导出方法都是通过传一个fieldMap参数(类的英文属性和Excel的中文列头的对应关系)来连接实体类和Excel

导出的时候可以选择导出到本地文件系统或导出到浏览器,也可以自定义每个工作表的大小

导入的时候可以自定义业务主键组合uniqueFields,这样就可以检测Excel中是否有重复行了

 

转自他人!!!!

你可能感兴趣的:(导入导出Excel工具类ExcelUtil)