美文网首页
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