美文网首页
Java导入Excel数据-实现通用方法

Java导入Excel数据-实现通用方法

作者: zbsong | 来源:发表于2020-03-06 11:41 被阅读0次

    一个新项目,做一个数据统计的功能,源数据需要从另外一个机构获取,获取方式和数据入库方式各种扯淡,源数据提供方只愿意通过EXCEL提供数据,项目部署后又不给操作数据库,关键数据还得每个月更新,所以只能先简单的做一个导入功能将源数据入库了。

    目前只是简单的实现了功能,有不正确的地方或者有更好的方法可以一起讨论。

    代码如下,不足之处望大神多多指教

    <html lang="en">
    <head>
        <meta charset="UTF-8"> 
        <title>Title</title>
    </head>
    <body>
    <table>
        <form action="/xxx/xxx/upload" method="post" enctype="multipart/form-data">
            <input type="file" name="file">
            <button type="submit">上传</button>
        </form>
    </table>
    </body>
    </html>
    

    原本使用的@Data注解,反射调用时,注解失效,后边再看一下,有大神知道的望不吝赐教

    @Component
    @Named("user")
    public class User {
        private String objId;
        private String name;
        private String sex;
        private Date createDate;
    
        public String getObjId() {
            return objId;
        }
    
        public void setObjId(String objId) {
            this.objId = objId;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public Date getCreateDate() {
            return createDate;
        }
    
        public void setCreateDate(Date createDate) {
            this.createDate = createDate;
        }
    }
    

    定义的枚举类,配置导入表对应的service和实体类在上下文中bean的id,有更好的方式的可以提出来哦

    public enum ImportEnum {
    
        //自行扩展,Student。。。。
        user("userService", "user", "USER");
    
        //获取对应service的bean ID
        private String serviceName;
    
        //对应的实体类的bean ID
        private String pojoName;
    
        //描述-表名称
        private String desc;
    
        private ImportEnum(String serviceName, String pojoName, String desc) {
            this.serviceName = serviceName;
            this.pojoName = pojoName;
            this.desc = desc;
        }
    
        public static String getServiceName(String desc) {
            ImportEnum[] carTypeEnums = values();
            for (ImportEnum carTypeEnum : carTypeEnums) {
                if (carTypeEnum.desc().equals(desc)) {
                    return carTypeEnum.serviceName();
                }
            }
            return null;
        }
    
        public static String getPojoName(String desc) {
            ImportEnum[] carTypeEnums = values();
            for (ImportEnum carTypeEnum : carTypeEnums) {
                if (carTypeEnum.desc().equals(desc)) {
                    return carTypeEnum.pojoName();
                }
            }
            return null;
        }
    
        private String serviceName() {
            return this.serviceName;
        }
    
        private String pojoName() {
            return this.pojoName;
        }
    
        private String desc() {
            return this.desc;
        }
    }
    

    把Map转为Java POJO对象,适用于对象包含List, Map, 数组, Date等

    @Component
    public class ReflectUtil {
        private Class<?> classBind;
    
        public ReflectUtil() {
        }
    
        public <T> void registerClass(Class<T> cl) {
            classBind = cl;
        }
    
        public Object handler(Map<String,Object> map, Object t) {
            ReflectUtil util = new ReflectUtil();
            util.registerClass(t.getClass());
            Iterator<Map.Entry<String, Object>> iter = map.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<String,Object> entry = iter.next();
                String key = entry.getKey();
                Object val = entry.getValue();
                util.setValue(key, val, t);
            }
            return t;
        }
    
        /**
         * 根据反射设置某字段的值
         *
         * @param name
         * @param value
         */
        public <T> void setValue(String name, Object value, Object ob) {
            try {
                if (null == value || null == name) {
                    return;
                }
                //PropertyDescriptor 属性描述器
                //获取bean的某个属性的描述符
                PropertyDescriptor pd = new PropertyDescriptor(name, this.classBind);
                //获得用于写入属性值的方法
                Method wM = pd.getWriteMethod();
                //获得属性的Class对象
                Class<?> parameterType = wM.getParameterTypes()[0];
                if (parameterType == String.class) {
                    // 写入属性值
                    wM.invoke(ob, String.valueOf(value));
                } else if (parameterType == int.class || parameterType == Integer.class) {
                    String str = String.valueOf(value);
                    if (StringUtils.isNotBlank(str)) {
                        wM.invoke(ob, new BigDecimal(String.valueOf(value)).intValue());
                    }
                } else if (parameterType == double.class || parameterType == Double.class) {
                    String str = String.valueOf(value);
                    if (StringUtils.isNotBlank(str)) {
                        wM.invoke(ob, new BigDecimal(String.valueOf(value)).doubleValue());
                    }
                } else if (parameterType == Date.class) {
                    String str = String.valueOf(value);
                    if (StringUtils.isNotBlank(str)) {
                        if (str.length() > 10) {
                            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
                            wM.invoke(ob, sdf.parse(str));
                        } else {
                            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                            wM.invoke(ob, sdf.parse(str));
                        }
                    }
                } else if (parameterType.isArray()) {
                    Object[] list = null;
                    if (value instanceof List) {
                        list = ((List<?>) value).toArray();
                    } else {
                        list = (Object[]) value;
                    }
                    Object[] args = new Object[list.length];
                    for (int index = 0; index < list.length; index++) {
                        Object item = list[index];
                        if (item instanceof Number) {
                            args[index] = item;
                        } else if (item instanceof String) {
                            args[index] = item;
                        } else {
                            args[index] = item;
                        }
                    }
                    wM.invoke(ob, args);
                } else {
                    Type[] parameters = wM.getGenericParameterTypes();
                    if (parameters.length > 0) {
                        if (parameters[0] instanceof ParameterizedType) {
                            ParameterizedType pt = (ParameterizedType) parameters[0];
                            Type[] actualTypeArguments = pt.getActualTypeArguments();
                            if (actualTypeArguments.length == 1) {
                                List<Object> args = new ArrayList<Object>();
                                Object[] list = null;
                                if (value instanceof List) {
                                    list = ((List<?>) value).toArray();
                                } else {
                                    list = (Object[]) value;
                                }
                                for (Object item : list) {
                                    if (item instanceof Number) {
                                        args.add(item);
                                    } else if (item instanceof String) {
                                        args.add(item);
                                    } else {
                                        args.add(item);
                                    }
                                }
                                wM.invoke(ob, args);
                            } else if (actualTypeArguments.length == 2) {
                                Map<Object, Object> targetMp = (Map<Object, Object>) value;
                                Map<Object, Object> args = new HashMap<>();
                                for (Object key : targetMp.keySet()) {
                                    Object val = targetMp.get(key);
                                    if (val instanceof Number) {
                                        args.put(key, val);
                                    } else if (val instanceof String) {
                                        args.put(key, val);
                                    } else {
                                        args.put(key, val);
                                    }
                                }
                                wM.invoke(ob, args);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                System.out.println("处理字段失败:" + name);
                e.printStackTrace();
            }
        }
    }
    
    @RestController
    @RequestMapping(value = "/xxx/xxx")
    public class ImportExcelController {
    
        Logger logger = LoggerFactory.getLogger(ImportExcelController.class);
    
        @Autowired
        private ImportExcelService importExcelService;
    
        @RequestMapping(value = "/upload", method = RequestMethod.POST)
        public String upload(@RequestParam("file") MultipartFile file) throws Exception {
            return importExcelServiceTwo.ImportData(file);
        }
    }
    
    @Service("userService")
    public class UserService {
        @Autowired
        UserMapper userMapper;
    
        //如果使用cl.newInstance()方式,mapper注入失败 解决方法 加入以下代码
        //public static UserService dynamicProxy;
    
        // @PostConstruct
        //public void init () {
        //    dynamicProxy = this;
        //}
    
        /**
         * 删除原有数据,导入新数据
         * @param date 日期,全量删除不需要,增量需要根据日期做对应的删除
         */
        public void delete(String date) {
            try {
                Map<String,Object> map = new HashMap<>();
                map.put("xxx",date);
                userMapper.deleteAll(map);
                //cl.newInstance()方式导致mapper注入失败的使用下面这种方式
                //dynamicProxy.userMapper.deleteAll(map);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 插入新数据
         * @param date 需要导入的数据
         */
        public void batchInserts(List<Object> date) {
            try {
                userMapper.batchInsertImpoer(date);
                //cl.newInstance()方式导致mapper注入失败的使用下面这种方式
                //userMapper.batchInsertImpoer(date);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    @Service
    public class ImportExcelServiceTwo {
    
        @Value("${batch.processing.number}")
        private int batchProcessingNumber;
    
        @Autowired
        private ApplicationContext context;
    
        /**
         * 开始导入数据
         * @param file
         * @return
         * @throws Exception
         */
        @Transactional(rollbackFor = Exception.class)
        public String ImportData (MultipartFile file) throws IOException {
            //获取文件名称,文件名称采用 表明#日期的格式,比如: USER#2020-03
            //数据是每个月都要导入,所以加一个日期区分,这个根据自己需求修改
            String fileName = file.getOriginalFilename();
            //表名,用来获取应用个上下文
            String tableName = fileName.substring(0,fileName.indexOf("#"));
            //获取数据的时间
            String date = fileName.substring(fileName.indexOf("#") + 1,fileName.indexOf("."));
            InputStream inputStream = file.getInputStream();
            Workbook book = null;
            if (isExcel2003(fileName)) {
                book = new HSSFWorkbook(inputStream);
            }
            if (isExcel2007(fileName)) {
                book = new XSSFWorkbook(inputStream);
            }
            Sheet sheet = book.getSheetAt(0);
            //总条数
            int allRowNum = sheet.getLastRowNum();
            Instant startNow = Instant.now();
            System.out.println("开始执行" + tableName + "数据");
            try {
                // 也可以使用Class.forName的方式获取,这种方式需要类对应的全部路径,不灵活
                // Class<?> cl = Class.forName(xxx.xxx.xxx.xxx.service);
                //ImportEnum 枚举类,用来获取导入表 对应的service和实体类的bean的Id
                Class<?> cl = context.getBean(ImportEnum.getServiceName(tableName)).getClass();
                //也可以使用Object o = cl.newInstance();的方式但是这种方式会导致mapper注入失效,需要单独处理,处理方法会在service中说明
                //使用context.getBean 直接从应用上下文中获取service
                Object o = context.getBean(ImportEnum.getServiceName(tableName));
                //获取删除数据方法,因为可能会重复导入某月数据,所以先删除操作,加了删除方法,根据自己实际需求来
                Method deleteM = cl.getDeclaredMethod("delete", String.class);
                //获取批量插入方法
                Method batchInsertM = cl.getDeclaredMethod("batchInserts", List.class);
                //先删除导入月份数据,防止数据重复
                //可能会重复导入某个月的数据,所以要先做对应的删除,然后在插入新数据
                //因为插入时是批量插入,所以把删除操作拿了出来,没有和新增放一起
                deleteM.invoke(o,date);
                //获取列头,用于后边将excel数据转换为实体数据
                Row row = sheet.getRow(0);
                String [] ColumnHeads = new String[300];
                IntStream.range(1, row.getLastCellNum()).forEach(i -> {
                    Cell cell = row.getCell(i);
                    cell.setCellType(CellType.STRING);
                    //将下划线转换为驼峰,与pojo对应,EXCEL是数据库导出来的数据,列头都是数据库字段名称,要和实体类保持一致 create_date -> createDate
                    //自己可以根据实际情况是否需要
                    if (StringUtils.isNotBlank(cell.getStringCellValue())) {
                        ColumnHeads[i] = Utils.lineToHump(cell.getStringCellValue().toLowerCase());
                    }
                });
                //批量处理新增
                //batchProcessingNumber代表每次批量插入条数,在配置文件中,自己视情况配置
                //根据批量处理条数,获取批量插入次数
                int pages = (allRowNum + batchProcessingNumber - 1) / batchProcessingNumber;
                Class w = context.getBean(ImportEnum.getPojoName(tableName)).getClass();
                Constructor c = w.getConstructor();
                for (int i = 1; i <= pages; i++) {
                    List<Object> lists = new ArrayList<>();
                    int fromIdx = (i - 1) * batchProcessingNumber;
                    int num = i * batchProcessingNumber > allRowNum ? allRowNum : (i * batchProcessingNumber);
                    //没有使用Stream 循环方式,异常后无法终止整个循环,改为for
                    for (int j = fromIdx + 1; j <= num; j++) {
                        Map<String, Object> beans = new HashMap<>();
                        //获取第j行数据
                        Row rowBean = sheet.getRow(j);
                        //将j行每列数据处理到map中
                        for (int k = 1; k < rowBean.getLastCellNum(); k++) {
                            Cell cell = rowBean.getCell(k);
                            cell.setCellType(CellType.STRING);
                            beans.put(ColumnHeads[k],cell.getStringCellValue());
                        };
                        try {
                            Object water = c.newInstance();
                            //也可以直接使用 Object object = ReflectUtil.handler(beans, water)实现
                            //原本想着后边系统使用AOP来做日志统一处理的,使用上面那种方式,通知会失效,所以采用了直接从应用上下文获取的方式
                            ReflectUtil reflectUtil = context.getBean(ReflectUtil.class);
                            Object object = reflectUtil.handler(beans, water);
                            lists.add(object);
                        } catch (Exception e) {
                            e.printStackTrace();
                            return tableName + "表导入数据异常,转换实体类关系失败:" + e.getMessage();
                        }
                    };
                    System.out.println("开始批量插入第" + (fromIdx + 1) + "到" + (num + 1) + "条数据");
                    batchInsertM.invoke(o,lists);
                }
                Instant endTime = Instant.now();
                System.out.println(tableName + "表数据插入结束,耗时" + Duration.between(startNow,endTime).toMillis()+"毫秒");
            } catch (Exception e) {
                e.printStackTrace();
                return tableName + "表导入数据异常" + e.getMessage();
            }
            return "success";
        }
    }
    
    @Mapper
    public interface UserMapper {
    
        int deleteAll(Map<String, Object> map);
    
        int batchInsertImpoer(List<Object> records);
    }
    

    使用的是Oracle数据库,批量插入写法根据实际使用数据库进行修改

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.sgcc.pms.mapper.UserMapper">
        <resultMap id="BaseResultMap" type="com.sgcc.pms.pojo.User">
            <id column="OBJ_ID" property="objId" jdbcType="VARCHAR"/>
            <result column="NAME" property="name" jdbcType="VARCHAR"/>
            <result column="SEX" property="sex" jdbcType="VARCHAR"/>
            <result column="CREATE_DATE" property="createDate" jdbcType="TIMESTAMP"/>
        </resultMap>
    
        <delete id="deleteAll" parameterType="map">
            delete from USERS 
        </delete>
    
        <insert id="batchInsertImpoer" parameterType="java.util.List">
            INSERT ALL
            <foreach collection="list" item="setting" index="index">
                into USERS (OBJ_ID, NAME, SEX, CREATE_DATE
                )
                values
                (#{setting.objId,jdbcType=VARCHAR}, #{setting.name,jdbcType=VARCHAR},
                #{setting.sex,jdbcType=VARCHAR}, #{setting.createDate,jdbcType=TIMESTAMP})
            </foreach>
            SELECT 1 FROM DUAL
        </insert>
    
    </mapper>
    

    相关文章

      网友评论

          本文标题:Java导入Excel数据-实现通用方法

          本文链接:https://www.haomeiwen.com/subject/ykfblhtx.html