• 关于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秒

  • 相关阅读:
    深入浅出java的Map
    退役划水(7)
    .NET 云原生架构师训练营(模块二 基础巩固 EF Core 关系)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 EF Core 基础与配置)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 EF Core 介绍)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 MySQL环境准备)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 MVC终结点)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 路由与终结点)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 HTTP管道与中间件)--学习笔记
    .NET 云原生架构师训练营(模块二 基础巩固 REST && RESTful)--学习笔记
  • 原文地址:https://www.cnblogs.com/zhengyuanyuan/p/10636862.html
Copyright © 2020-2023  润新知