關於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("\t");
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 往 StringBuilder 里追加一條數據的最后一個字段。
     *
     * @param builder StringBuilder。
     * @param object  數據。
     */
    public void builderEnd(StringBuilder builder, Object object) {
        builder.append(object);
        builder.append("\n");
    }
 
    /*-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --*/
 
    /**
     * 通過 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秒

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM