• 反射+自定义注解---实现Excel数据列属性和JavaBean属性的自动映射


    简单粗暴,直奔主题。

         需求:通过自定义注解和反射技术,将Excel文件中的数据自动映射到pojo类中,最终返回一个List<pojo>集合?

      今天我只是通过一位使用者的身份来给各位分享一套超级可以的POI“工具”,这套工具我只是第一个使用者,创作者是我的朋友,他喜好钻研底层和算法,擅长计算机软硬件,在我心里他一直是神一样的存在,每天晚上10点后我才能看到他,因为他每天需要加班,需要有更多时间能够学习,唉,这种毅力和耐力,我是真的羡慕,因为我也一直在努力,能够得到更多的东西。

      首先关于jar的管理,我就不多说了,导入和POI相关的jar包即可。第一我给大家分享的是一个他封装好的工具类,原理是通过获取到Excel文件,然后通过你指定的pojo对象,他就会自动封装。这套代码也就400行左右,说真的用点心瞅瞅完全有必要看懂,不多说了,我看了半天,自己也能说得通他是怎么写的,更详细的我也想给各位补补,但是无能为力啊。

      1 public class ExcelUtil {
      2 
      3     public String defaultDateFormat = "yyyy-MM-dd HH:mm:ss";
      4 
      5     /**
      6      * 将excel表格中的信息设置进bean中
      7      * 
      8      * @param file
      9      * @param t
     10      * @return
     11      * @throws Exception
     12      * @Date 2017年6月13日
     13      */
     14     public <T> T setExcelInfo2Bean(File file, T t) {
     15         // 获取工作簿类下的子类(表类)
     16         Field[] declaredFields = t.getClass().getDeclaredFields();
     17 
     18         for (int i = 0; i < declaredFields.length; i++) {
     19             Field sheetFiled = declaredFields[i];
     20             sheetFiled.setAccessible(true);
     21             // 将子表的内容赋值到对象中
     22             try {
     23                 sheetFiled.set(t, setSheetValue2Bean(sheetFiled, file));
     24             } catch (Exception e) {
     25                 e.printStackTrace();
     26             }
     27         }
     28         return t;
     29     }
     30 
     31     /**
     32      * 校验参数的类中是否包含ExcelSheetName注解
     33      * 
     34      * @param declaredFields
     35      * @Date 2017年6月13日
     36      */
     37     public <T extends Annotation> Field[] matchDeclaredFields(Field[] declaredFields, Class T) {
     38         List<Field> matchedDeclaredFieldsList = new ArrayList<Field>();
     39 
     40         for (int i = 0; i < declaredFields.length; i++) {
     41 
     42             Field sheetFiled = declaredFields[i];
     43             sheetFiled.setAccessible(true);
     44             if (sheetFiled.getAnnotation(T) != null) {
     45                 matchedDeclaredFieldsList.add(sheetFiled);
     46             }
     47         }
     48         Field[] matchedDeclaredFieldsArray = null;
     49 
     50         if (matchedDeclaredFieldsList.size() > 0) {
     51             matchedDeclaredFieldsArray = new Field[matchedDeclaredFieldsList.size()];
     52 
     53             for (int i = 0; i < matchedDeclaredFieldsArray.length; i++) {
     54                 matchedDeclaredFieldsArray[i] = matchedDeclaredFieldsList.get(i);
     55             }
     56         }
     57 
     58         return matchedDeclaredFieldsArray;
     59 
     60     }
     61 
     62     /**
     63      * 将子表的内容赋值到对象中
     64      * 
     65      * @param sheetFiled
     66      * @param file
     67      * @return
     68      * @throws Exception
     69      * @Date 2017年6月8日
     70      */
     71     private <T> Object setSheetValue2Bean(Field sheetFiled, File file) throws Exception {
     72         // 薄类中所有参数均为list类型,不进行校验
     73         Class sheetListClass = sheetFiled.getType();
     74         // 创建集合对象
     75         // List sheetList = (List) sheetListClass.newInstance();
     76         // 获取参数的类型的参数化的类型
     77         Type type = sheetFiled.getGenericType();
     78         // 将参数化的类型强转,获得类型中的参数(泛型中的类)
     79         ParameterizedType pType = (ParameterizedType) type;
     80         // 泛型中的参数,如果是map,数组长度就为2
     81         Type[] listType = pType.getActualTypeArguments();
     82         // 获取list泛型中的子表class
     83         Class sheetClass = (Class) listType[0];
     84 
     85         // 获取子类对应的sheet名
     86         ExcelSheetName sheetNameAnno = (ExcelSheetName) sheetClass.getAnnotation(ExcelSheetName.class);
     87 
     88         String sheetName = sheetNameAnno.value();
     89 
     90         // 获取文件后缀
     91         String fileExt = file.getName().substring(file.getName().lastIndexOf(".") + 1);
     92         // 创建流
     93         InputStream input = new FileInputStream(file);
     94 
     95         // 创建Workbook
     96         Workbook wb = null;
     97 
     98         // 创建sheet
     99         Sheet sheet = null;
    100 
    101         // 根据后缀判断excel 2003 or 2007+
    102         if (fileExt.equals("xls")) {
    103             wb = (HSSFWorkbook) WorkbookFactory.create(input);
    104         } else {
    105             wb = new XSSFWorkbook(input);
    106         }
    107 
    108         // 获取表
    109         sheet = wb.getSheet(sheetName);
    110         // 获取行数
    111 
    112         return getExcelInfo2Bean(sheetClass, sheet);
    113     }
    114 
    115     /**
    116      * 将返回与sheet内容对应的class的实例的List集合
    117      * 
    118      * @param sheetClass
    119      * @param sheet
    120      * @throws Exception
    121      * @Date 2017年6月13日
    122      */
    123     private <T extends ExcelCheckPropertie> List<T> getExcelInfo2Bean(Class T, Sheet sheet) throws Exception {
    124         Map<String, Integer> cellNameMap = getCellNameMap(sheet);
    125 
    126         // 获取行数
    127         int rowNum = sheet.getLastRowNum();
    128         if (rowNum == 0) {
    129             return new ArrayList<T>();
    130         }
    131 
    132         List<T> tList = new ArrayList<T>(rowNum - 1);
    133 
    134         // 获取子表类的属性(对应表中的列)
    135         Field[] colFields = T.getDeclaredFields();
    136         Field[] excelCheckPropertiesDeclaredFields = T.getSuperclass().getDeclaredFields();
    137         // (获取只包含自定义注解的属性)
    138         Field[] matchedColFields = matchDeclaredFields(colFields, ExcelColName.class);
    139         // 如果包含自定义注解的参数
    140 
    141         // 从第二行开始读取,并设置进实例
    142         for (int j = 1; j <= rowNum; j++) {
    143             Row row = sheet.getRow(j);
    144             if (row == null) {
    145                 continue;
    146             }
    147             // 创建当前sheet类的实例
    148             T sheetBean = (T) T.newInstance();
    149 
    150             // 遍历包含自定义注解的参数
    151             if (matchedColFields != null && matchedColFields.length > 0) {
    152 
    153                 for (int i = 0; i < matchedColFields.length; i++) {
    154                     matchedColFields[i].setAccessible(true);
    155                     Field colField = matchedColFields[i];
    156 
    157                     ExcelColName excelColNameAnno = colField.getAnnotation(ExcelColName.class);
    158                     String excelColName = excelColNameAnno.value().trim();
    159                     // 判断该参数是否需要校验
    160                     boolean isRequired = excelColNameAnno.IsRequired();
    161                     // 如果为必填字段
    162                     if (isRequired) {
    163                         // 遍历每行的每个参数,设置进bean
    164                         for (int k = 0; k < row.getPhysicalNumberOfCells(); k++) {
    165 
    166                             // 获取sheet类的属性对应的表中的列的cell对象
    167                             Cell cell = row.getCell(cellNameMap.get(excelColName));
    168                             String cellValue = "";
    169                             if (cell != null) {
    170                                 cellValue = getCellValue(cell);
    171 
    172                                 // 判断属性类型
    173                                 if (matchedColFields[i].getType().isAssignableFrom(Integer.class)) {
    174                                     matchedColFields[i].set(sheetBean, Integer.parseInt(getCellValue(cell)));
    175 
    176                                 } else if (matchedColFields[i].getType().isAssignableFrom(Date.class)) {
    177                                     matchedColFields[i].set(sheetBean, getDateCellValue(cell));
    178 
    179                                 } else if (matchedColFields[i].getType().isAssignableFrom(Double.class)) {
    180                                     matchedColFields[i].set(sheetBean, Double.parseDouble(getCellValue(cell)));
    181 
    182                                 } else if (matchedColFields[i].getType().isAssignableFrom(Float.class)) {
    183                                     matchedColFields[i].set(sheetBean, Float.parseFloat(getCellValue(cell)));
    184 
    185                                 } else {
    186                                     matchedColFields[i].set(sheetBean, getCellValue(cell));
    187                                 }
    188                             }
    189 
    190                             // 设置父类属性
    191                             for (int l = 0; l < excelCheckPropertiesDeclaredFields.length; l++) {
    192                                 Field superField = excelCheckPropertiesDeclaredFields[l];
    193                                 superField.setAccessible(true);
    194                                 // 当前单元格所在表名
    195                                 if (superField.getName().equals("sheetName")) {
    196                                     superField.set(sheetBean, sheet.getSheetName());
    197                                     // 当前单元格所在行数
    198                                 } else if (superField.getName().equals("rowNum")) {
    199                                     superField.set(sheetBean, j);
    200                                     // 当前单元格所在列名
    201                                 } else if (superField.getName().equals("colName")) {
    202                                     superField.set(sheetBean, excelColName);
    203                                     // 非空校验结果
    204                                 } else if (superField.getName().equals("isChecked")) {
    205                                     if (cellValue == null || "".equals(cellValue.trim())) {
    206                                         superField.set(sheetBean, false);
    207                                     }
    208 
    209                                 }
    210                             }
    211 
    212                         }
    213                     } else {
    214                         // 遍历每行的每个参数,设置进bean
    215                         for (int k = 0; k < row.getPhysicalNumberOfCells(); k++) {
    216 
    217                             // 获取sheet类的属性对应的表中的列的cell对象
    218                             if (excelColName.equals("上传时间")) {
    219                                 System.out.println();
    220                             }
    221                             Integer integer = cellNameMap.get(excelColName);
    222                             Cell cell = row.getCell(integer);
    223                             if (cell != null) {
    224                                 // 设置父类属性
    225                                 for (int l = 0; l < excelCheckPropertiesDeclaredFields.length; l++) {
    226                                     Field superField = excelCheckPropertiesDeclaredFields[l];
    227                                     superField.setAccessible(true);
    228                                     // 当前单元格所在表名
    229                                     if (superField.getName().equals("sheetName")) {
    230                                         superField.set(sheetBean, sheet.getSheetName());
    231                                         // 当前单元格所在行数
    232                                     } else if (superField.getName().equals("rowNum")) {
    233                                         superField.set(sheetBean, j);
    234                                         // 当前单元格所在列名
    235                                     } else if (superField.getName().equals("colName")) {
    236                                         superField.set(sheetBean, excelColName);
    237                                     }
    238                                 }
    239                                 // 判断属性类型
    240                                 if (matchedColFields[i].getType().isAssignableFrom(Integer.class)) {
    241                                     matchedColFields[i].set(sheetBean, Integer.parseInt(getCellValue(cell)));
    242 
    243                                 } else if (matchedColFields[i].getType().isAssignableFrom(Date.class)) {
    244                                     matchedColFields[i].set(sheetBean, getDateCellValue(cell));
    245 
    246                                 } else if (matchedColFields[i].getType().isAssignableFrom(Double.class)) {
    247                                     matchedColFields[i].set(sheetBean, Double.parseDouble(getCellValue(cell)));
    248 
    249                                 } else if (matchedColFields[i].getType().isAssignableFrom(Float.class)) {
    250                                     matchedColFields[i].set(sheetBean, Float.parseFloat(getCellValue(cell)));
    251 
    252                                 } else {
    253                                     matchedColFields[i].set(sheetBean, getCellValue(cell));
    254                                 }
    255                             }
    256                         }
    257                     }
    258 
    259                 }
    260             }
    261             tList.add(sheetBean);
    262         }
    263 
    264         // 校验空值
    265         ListIterator<T> listIterator = tList.listIterator();
    266         while (listIterator.hasNext()) {
    267             T next = listIterator.next();
    268             int nullNum = 0;
    269             for (int i = 0; i < matchedColFields.length; i++) {
    270                 if (matchedColFields[i].get(next) == null || matchedColFields[i].get(next).toString().equals("")) {
    271                     ++nullNum;
    272                 }
    273             }
    274             if (nullNum == matchedColFields.length) {
    275                 // System.out.println("已删除一个元素");
    276                 listIterator.remove();
    277             }
    278         }
    279 
    280         return tList;
    281 
    282     }
    283 
    284     /**
    285      * 获取时间类型数值 cell.getCellStyle().getDataFormat() 日期时间(yyyy-MM-dd HH:mm:ss) -
    286      * 22, 日期(yyyy-MM-dd) - 14, 时间(HH:mm:ss) - 21, 年月(yyyy-MM) - 17, 时分(HH:mm) -
    287      * 20, 月日(MM-dd) - 58
    288      * 
    289      * @param cell
    290      * @return
    291      * @Date 2017年6月13日
    292      */
    293     private Date getDateCellValue(Cell cell) {
    294         return cell.getDateCellValue();
    295     }
    296 
    297     /**
    298      * 获取第一行做标题存入列名与对应的列值
    299      * 
    300      * @param sheet
    301      * @return
    302      * @Date 2017年6月13日
    303      */
    304     public Map<String, Integer> getCellNameMap(Sheet sheet) {
    305         // 获取第一行列的列名及列数存入map
    306         Map<String, Integer> colNameMap = new HashMap<String, Integer>();
    307         Row firstRow = sheet.getRow(0);
    308         // 列数
    309         int cellNum = firstRow.getLastCellNum();
    310         // map赋值
    311         for (int i = 0; i < cellNum; i++) {
    312             colNameMap.put(getCellValue(firstRow.getCell(i)), i);
    313         }
    314 
    315         return colNameMap;
    316     }
    317 
    318     /**
    319      * 对Excel的各个单元格的格式进行判断并转换
    320      */
    321     private String getCellValue(Cell cell) {
    322         String cellValue = "";
    323         DecimalFormat df = new DecimalFormat("####################.##########");
    324         switch (cell.getCellType()) {
    325         case HSSFCell.CELL_TYPE_STRING:
    326             cellValue = cell.getRichStringCellValue().getString().trim();
    327             break;
    328         case HSSFCell.CELL_TYPE_NUMERIC:
    329             if (HSSFDateUtil.isCellDateFormatted(cell)) {
    330                 Date date = cell.getDateCellValue();
    331                 cellValue = new SimpleDateFormat(defaultDateFormat).format(date);
    332             } else {
    333                 double dc = cell.getNumericCellValue();
    334                 // cellValue = String.valueOf(dc);
    335                 cellValue = df.format(dc);
    336             }
    337             break;
    338         case HSSFCell.CELL_TYPE_BOOLEAN:
    339             cellValue = String.valueOf(cell.getBooleanCellValue()).trim();
    340             break;
    341         case HSSFCell.CELL_TYPE_FORMULA:
    342             cellValue = String.valueOf(cell.getNumericCellValue());
    343             break;
    344 
    345         default:
    346             cellValue = "";
    347         }
    348         return cellValue;
    349     }
    350 
    351 }
    工具类

      接着就是俩个自定义注解分别是:@ExcelSheetName@ExcelColName,这俩个注解都是放在pojo类上的。第一个主要是标注和Excel文件中那张sheet表,第二个主要是将Excel文件中的列名和pojo类的对应属性绑定,具体用法瞅瞅我下面贴的代码就OK。

     1 /**
     2  * 用于匹配Excel文件中的sheet表名(注解值必须是Excel文件中sheet表名)
     3  * @author zxz
     4  *
     5  */
     6 @Documented
     7 @Inherited
     8 @Retention(RetentionPolicy.RUNTIME)
     9 public @interface ExcelSheetName {
    10     String value() default "";
    11 }
    View Code
     1 /**
     2  * 用于匹配Excel表中的列(vlaue值必须是Excel文件中第一行的列名)
     3  * @author zxz
     4  *
     5  */
     6 @Documented
     7 @Target(ElementType.FIELD)
     8 @Inherited
     9 @Retention(RetentionPolicy.RUNTIME)
    10 public @interface ExcelColName {
    11     String value() default "";
    12     boolean IsRequired() default false;
    13 }
    View Code

      具体是如何使用自定义注解将pojo类和Excel文件中的数据完成自动映射的,请参考下面pojo类代码。

     1 /**
     2  * 商品
     3  * @author zxz
     4  *
     5  */
     6 @ExcelSheetName("商品信息")
     7 public class Item extends ExcelCheckPropertie implements Serializable {
     8     
     9     private String id;                //主键
    10     @ExcelColName(value="商品名称",IsRequired=true)        //IsRequired=true表示非空
    11     private String itemName;        //商品名称
    12     @ExcelColName(value="价格")
    13     private Double price;            //价格
    14     @ExcelColName(value="描述")
    15     private String itemDesc;        //描述
    16     @DateTimeFormat(pattern="yyyy-MM-dd")
    17     @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    18     @ExcelColName(value="上架时间")
    19     private Date createTime;        //添加时间
    Item

      最后,我是将这套东西整合到我的一个数据录入小项目中,因为之前导入一张600条数据的文件时,速度就很慢,一直是我的心头病,不过这次杠杠的。那天下午我整合成功后,心里一直乐到下班,因为最后进行了一套小小的性能和速度测试,结果美滋滋。我调用工具类中的方法进行数据的自动映射,数据10000条,最终导入到数据库中全程使用了7分钟,各位是不是觉得时间还是有点长,但是这个过程我是即把这10000多条的数据封装进来了而且还成功插入到数据库中去了,我想这个结果应该能及格吧,如果各位还不能接受这个速度,那可以优化数据库的读写速度,效果可能会更好。需要特别说明一点的是:将Excel文件中的数据封装到数据集合中只需3秒多一点,我反正是够用了,哈哈~~

      我的数据最后是封装到一个结果处理Vo类中。

     1 import java.io.Serializable;
     2 import java.util.List;
     3 
     4 public class ItemVo implements Serializable {
     5     
     6     private List<Item> listItem;
     7 
     8     public List<Item> getListItem() {
     9         return listItem;
    10     }
    11 
    12     public void setListItem(List<Item> listItem) {
    13         this.listItem = listItem;
    14     }
    15 
    16     @Override
    17     public String toString() {
    18         return "ItemVo [listItem=" + listItem + "]";
    19     }
    20 }
    ItemVo
     1 @Controller
     2 @RequestMapping("/poi")
     3 public class MainPOIAction {
     4     
     5     @Autowired
     6     private ItemService itemService;
     7     
     8     /**
     9      * 自动映射Excel文件和javaBean对象的属性封装
    10      * @return
    11      */
    12     @RequestMapping(value = "/autoMapping",produces = "text/plain;charset=UTF-8")
    13     @ResponseBody
    14     public String autoMapping(){
    15         ExcelUtil eu = new ExcelUtil();
    16         // 开始导入时间
    17         long starTime=System.nanoTime();
    18         // 将指定路径下Excel文件中的数据自动封装到Bean对象中
    19         ItemVo itemVo = eu.setExcelInfo2Bean(new File("H://POI开发//商品信息模板.xlsx"), new ItemVo());
    20         List<Item> listItem = itemVo.getListItem();
    21         /*for (Item item : listItem) {
    22             int save = itemService.saveItem(item);
    23             if(save != 1){
    24                 System.out.println("商品ID"+item.getId()+"导入失败");
    25                 continue;
    26             }
    27         }*/
    28         // 导入结束时间
    29         long endTime=System.nanoTime();
    30         long time = endTime-starTime;
    31         return JsonUtil.object2Json(time);
    32     }
    33 
    34 }
    main

      纯属抱大腿,但是也学到了不少东西,希望能给各位博友带来灵感。

  • 相关阅读:
    react 调用webIm
    css样式问题解决
    学习animejs
    vue,在模块中动态添加dom节点,并监听
    vue 关于solt得用法
    vue-cli 安装过程出现错误
    处理参数中存在多个连续空格,只显示一个空格,复制后搜索不到得问题
    http StatusCode(状态码)
    修改表单小技巧
    关于swiper中包含表单元素的bug
  • 原文地址:https://www.cnblogs.com/1315925303zxz/p/7028353.html
Copyright © 2020-2023  润新知