zoukankan      html  css  js  c++  java
  • 关于mybatis使用foreach插入速度较慢的问题

    使用mybatis批量插入,看了这篇博客

    https://blog.csdn.net/m0_37981235/article/details/79131493

    我这种懒货懒得想其中原因,直接上手第三种!

    结果测试多次,发现我插入8000条数据,第一种方式只需要30秒不到,可是第三种方法却需要一分多钟。

    不知道原作者是怎么实现的,可能和插入数据的多少有关,我这里是8个字段。测试后1000taio需要1.7秒,500条需要0.4秒,切割成100条100条的插入,最终8000条数据只需要1.4s.

    查询资料得知

     Mybatis 在解析 foreach 的时候,因为需要循环解析 #{} 之类的占位符,foreach 的集合越大,解析越慢。

    mybatis进行foreach的时候是没有缓存的,每次都得重新解析一下,所以越来越慢。

    如果想要达到上图的程度,必须先解决foreach拼接问题

    我没有找到好的办法。如果有大佬知道还请告知。

    所以我可以java中使用字符串拼接的方式。

    但是即使拼接成字符串之后,速度可能的确会很快,但是没办法在mybatis中引用。

    例如

    insert into tb_csp_baseDataAll (
            parentArea,
            area,
            committee,
            type,
            woman_name,
            woman_id_card,
            tel,
            wid
            ) values #{string}

    这种传入的string就是字符串,带有的()是在字符串内的,可能

     类似于

    insert into tb_csp_baseDataAll (
            parentArea,
            area,
            committee,
            type,
            woman_name,
            woman_id_card,
            tel,
            wid
            ) values "('a','b'...)"

    数据库会报错,暂时没有找到好的办法解决。

    如果使用存储过程的话,传入的值为为一个list是不大可能的,最理想的方法估计就是传一个json字符串,然后进行解析,似乎也是非常的麻烦,效率也不一定会很高。

    使用批处理方式解决,8000条数据也是需要22秒左右。

    最后查阅资料使用LOAD DATA LOCAL INFILE实现大批量插入:https://blog.csdn.net/baidu_38083619/article/details/83378885

    MySQL使用LOAD DATA LOCAL INFILE从文件中导入数据比insert语句要快,MySQL文档上说要快20倍左右。
    但是这个方法有个缺点,就是导入数据之前,必须要有文件,也就是说从文件中导入。这样就需要去写文件,以及文件删除等维护。某些情况下,比如数据源并发的话,还会出现写文件并发问题,很难处理。
    那么有没有什么办法,可以达到同样的效率,直接从内存(IO流中)中导入数据,而不需要写文件呢?


    MySQL社区提供这样一个方法:setLocalInfileInputStream(),此方法位于com.mysql.jdbc.PreparedStatement 类中。通过使用 MySQL JDBC 的setLocalInfileInputStream 方法实现从Java InputStream中load data local infile 到MySQL数据库中。

    package com.akb.hfcx.csp.utils;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    import javax.annotation.Resource;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Component;
    
    @Component
    public class LoadDataInFileUtil {
     
        private Logger logger = LoggerFactory.getLogger(LoadDataInFileUtil.class);
        private Connection conn = null;
        @Resource
        private JdbcTemplate jdbcTemplate;
     
        /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
     
        /**
         * 将数据从输入流加载到MySQL。
         *
         * @param loadDataSql  SQL语句。
         * @param dataStream   输入流。
         * @param jdbcTemplate JDBC。
         * @return int         成功插入的行数。
         */
        private int bulkLoadFromInputStream(String loadDataSql,
                                            InputStream dataStream,
                                            JdbcTemplate jdbcTemplate) throws SQLException {
            if (null == dataStream) {
                logger.info("输入流为NULL,没有数据导入。");
                return 0;
            }
            conn = jdbcTemplate.getDataSource().getConnection();
            PreparedStatement statement = conn.prepareStatement(loadDataSql);
            int result = 0;
            if (statement.isWrapperFor(com.mysql.jdbc.Statement.class)) {
                com.mysql.jdbc.PreparedStatement mysqlStatement = statement.unwrap(com.mysql.jdbc.PreparedStatement.class);
                mysqlStatement.setLocalInfileInputStream(dataStream);
                result = mysqlStatement.executeUpdate();
            }
            return result;
        }
     
        /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
     
        /**
         * 组装 SQL 语句。
         *
         * @param dataBaseName 数据库名。
         * @param tableName    表名。
         * @param columnName   要插入数据的列名。
         */
        public String assembleSql(String dataBaseName, String tableName, String columnName[]) {
            String insertColumnName = StringUtils.join(columnName, ",");
            String sql = "LOAD DATA LOCAL INFILE 'sql.csv' IGNORE INTO TABLE " + dataBaseName + "." + tableName + "(" + insertColumnName + ")";
            return sql;
        }
     
        /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
     
        /**
         * 往 StringBuilder 里追加数据。
         *
         * @param builder StringBuilder。
         * @param object  数据。
         */
        public void builderAppend(StringBuilder builder, String object) {
            builder.append(object);
            builder.append("	");
        }
     
        /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
     
        /**
         * 往 StringBuilder 里追加一条数据的最后一个字段。
         *
         * @param builder StringBuilder。
         * @param object  数据。
         */
        public void builderEnd(StringBuilder builder, Object object) {
            builder.append(object);
            builder.append("
    ");
        }
     
        /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
     
        /**
         * 通过 LOAD DATA LOCAL INFILE 大批量导入数据到 MySQL。
         *
         * @param sql     SQL语句。
         * @param builder 组装好的数据。
         */
        public int fastInsertData(String sql, StringBuilder builder) {
            int rows = 0;
            InputStream is = null;
            try {
                byte[] bytes = builder.toString().getBytes();
                if (bytes.length > 0) {
                    is = new ByteArrayInputStream(bytes);
                    //批量插入数据。
                    long beginTime = System.currentTimeMillis();
                    rows = bulkLoadFromInputStream(sql, is, jdbcTemplate);
                    long endTime = System.currentTimeMillis();
                    logger.info("LOAD DATA LOCAL INFILE :【插入" + rows + "行数据至MySql中,耗时" + (endTime - beginTime) + "ms。】");
                }
     
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (null != is) {
                        is.close();
                    }
                    if (null != conn) {
                        conn.close();
                    }
                } catch (IOException | SQLException e) {
                    e.printStackTrace();
                }
            }
            return rows;
        }
    }

    调用部分方法:

        public void addList(List<AllBaseData> list) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            StringBuilder sb = new StringBuilder();
            for (AllBaseData entity : list) {
                loadDataInFileUtil.builderAppend(sb, entity.getParentArea());
                loadDataInFileUtil.builderAppend(sb, entity.getArea());
                loadDataInFileUtil.builderAppend(sb, entity.getCommittee());
                loadDataInFileUtil.builderAppend(sb, entity.getType());
                loadDataInFileUtil.builderAppend(sb, entity.getWoman_name());
                loadDataInFileUtil.builderAppend(sb, entity.getWoman_id_card());
                loadDataInFileUtil.builderAppend(sb, entity.getTel());
                loadDataInFileUtil.builderEnd(sb, entity.getWid());
            }
            
            String sql = loadDataInFileUtil.assembleSql(DATA_BASE_NAME, TABLE_NAME, COLUMN_NAME);
            int insertRow = loadDataInFileUtil.fastInsertData(sql, sb);
            System.out.println("insert应收报表数量insertRow:"+insertRow);
            stopWatch.stop();
            System.out.println("花费时间" + stopWatch.getTotalTimeSeconds());
            
        }

    且不要忘记在spring的配置文件加上

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

    dataSource是数据库相关配置bean

    测试8000条数据只需要0.214秒

  • 相关阅读:
    jackson 枚举 enum json 解析类型 返回数字 或者自定义文字 How To Serialize Enums as JSON Objects with Jackson
    Antd Pro V5 中ProTable 自定义查询参数和返回值
    ES6/Antd 代码阅读记录
    es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
    Antd Hooks
    使用.Net Core开发WPF App系列教程(其它 、保存控件内容为图片)
    使用.Net Core开发WPF App系列教程( 三、与.Net Framework的区别)
    使用.Net Core开发WPF App系列教程( 四、WPF中的XAML)
    使用.Net Core开发WPF App系列教程( 二、在Visual Studio 2019中创建.Net Core WPF工程)
    使用.Net Core开发WPF App系列教程( 一、.Net Core和WPF介绍)
  • 原文地址:https://www.cnblogs.com/zhengyuanyuan/p/10636862.html
Copyright © 2011-2022 走看看