美文网首页
百万级数据导入(oracle)

百万级数据导入(oracle)

作者: Yluozi | 来源:发表于2022-06-16 11:00 被阅读0次

前言:
最近有需求,做一个web端的Excel数据导入的功能,涉及到百万级数据体量的导入,1)excel实现可供选择的是poi和easyexcel实现(因为项目中已依赖poi的低版本依赖,引入easyexcel会提示依赖包冲突,最终选择poi实现,本人后续会记录具体功能实现代码),2)数据库处理及存入上因为涉及到大量数据,本人使多线程方法执行,在存入数据库过程,本人先使用的还是mybatis-plus实现,实现效果比较差:不到百万的数据,导入时间超过半小时,最后采用jdbc的批量导入来实现的该功能,线程连接池使用c3p0实现,具体项目demo后续提供。

其中:
多线程上未使用线程池,代码如下:

引入import java.util.concurrent.CountDownLatch;
private CountDownLatch countDownLatch;

        long start = Calendar.getInstance().getTimeInMillis();//开始时间,单位是毫秒
                 // 在这里 list 需要复制 需要插入数据库的实例集合
                // 异步多线程  插入数据库
                int total = list.size();    
                //每40000条数据,调用一次线程(具体数量配置看服务器或本地cpu及性能调整)  
                int batchSize = 40000;
                int number =total%batchSize ==0 ?total / batchSize :total / batchSize+1;
                countDownLatch = new CountDownLatch(number);
                for(int i = 0;i<number;i++){
                    List<Map<String, Object>> batchList = new ArrayList<>();
                    if(i== number-1){  // 最后一个
                        batchList = list.subList(i*batchSize,total);
                    }else{
                        batchList = list.subList(i*batchSize,(i+1)*batchSize);
                    }
                    /*创建多线程*/
                    logger.info("启动多线程:");
                    this.asyncBatchAddXml(countDownLatch,batchList);
                }
            }
            long end = Calendar.getInstance().getTimeInMillis();//结束时间
            double spentTime = (double) end - end1; //末减初就是所花时间
            logger.info("===数据导入时间:"+spentTime+"共计:"+list.size()+"条成功数据");
            /*更新导入文件表内容*/

list为 List<Map<String, Object>> list = new ArrayList<>();是导入的全量数据

    @Async("asyncServiceExecutor")
    public Integer asyncBatchAddXml(CountDownLatch countDownLatch, List<Map<String, Object>> batchList) {

        try {
            int  count = insertAll("plugging_url", batchList);
            if(count>0){
                return Integer.valueOf(count);
            }else{
                return 0;
            }
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
            return 0;
        }finally {
            countDownLatch.countDown();
        }
    }

asyncBatchAddXml为多线程调用方法


    /**
     * 执行数据库插入操作 4   * @param datas     插入数据表中key为列名和value为列对应的值的Map对象的List集合
     * @param tableName 要插入的数据库的表名
     * @return 影响的行数
     * @throws SQLException SQL异常
     */
    public int insertAll(String tableName, List<Map<String, Object>> datas) throws SQLException {
        /**影响的行数**/
        int affectRowCount = -1;
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            /**从数据库连接池中获取数据库连接**/
            connection = dataSource.getConnection();
            Map<String, Object> valueMap = datas.get(0);
            /**获取数据库插入的Map的键值对的值**/
            Set<String> keySet = valueMap.keySet();
            Iterator<String> iterator = keySet.iterator();
            /**要插入的字段sql,其实就是用key拼起来的**/
            StringBuilder columnSql = new StringBuilder();
            /**要插入的字段值,其实就是?**/
            StringBuilder unknownMarkSql = new StringBuilder();
            Object[] keys = new Object[valueMap.size()];
            int i = 0;
            while (iterator.hasNext()) {
                String key = iterator.next();
                keys[i] = key;
                columnSql.append(i == 0 ? "" : ",");
                columnSql.append(key);

                unknownMarkSql.append(i == 0 ? "" : ",");
                /*时间格式需要转换*/
                if(key.contains("time")||key.contains("TIME")){
                    unknownMarkSql.append("to_date(?,'yyyy-mm-dd hh24:MI')");
                }else{
                    unknownMarkSql.append("?");
                }

                i++;
            }
            /**开始拼插入的sql语句**/
            StringBuilder sql = new StringBuilder();
            sql.append("INSERT INTO ");
            sql.append(tableName);
            sql.append(" (");
            sql.append(columnSql);
            sql.append(" )  VALUES (");
            sql.append(unknownMarkSql);
            sql.append(" )");

            /**执行SQL预编译**/
            preparedStatement = connection.prepareStatement(sql.toString());
            /**设置不自动提交,以便于在出现异常的时候数据库回滚**/
            connection.setAutoCommit(false);
            System.out.println(sql.toString());
            for (int j = 0; j < datas.size(); j++) {
                for (int k = 0; k < keys.length; k++) {
                    preparedStatement.setObject(k + 1, datas.get(j).get(keys[k]));
                }
                preparedStatement.addBatch();
            }
            int[] arr = preparedStatement.executeBatch();
            connection.commit();
            affectRowCount = arr.length;
            System.out.println("JDBC成功了插入了"+affectRowCount+"行");
        } catch (Exception e) {
            if (connection != null) {
                connection.rollback();
            }
            e.printStackTrace();
            throw e;
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
            if (connection != null) {
                connection.close();
            }
        }
        return affectRowCount;
    }

insertAll为JDBC批量导入方法,其中数据库使用c3p0线程池连接

效果实际:


image.png

300W+的数据,文件大小在60mb,文件上传时间3min,处理时间2.3min

相关文章

网友评论

      本文标题:百万级数据导入(oracle)

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