💍 背景介绍
在最近的开发过程中,遇到了往数据库中表中插入大量的数据。有一个全国银行各分行的信息,共计148032条数据
文件有8.45MB,因为考虑到数据量比较大,就想着导入到MySQL看一看需要多长时间。
💍 方案一:用 for语句循环插入(不推荐)
使用for循环语句将,将数据一条条插入。
sql
insert into t_bank values (?, ?, ?, ?, ?)
java
/**
* 导入银行信息
*
* @param bankList
* @return java.lang.String
*/
@Override
public String importBank(List<TBank> bankList) {
if (StringUtils.isNull(bankList) || bankList.size() == 0)
{
throw new CustomException("导入用户数据不能为空!");
}
long start = System.currentTimeMillis();
for (int i = 0; i < bankList.size(); i++) {
tBankMapper.insertTBank(bankList.get(i));
}
long end = System.currentTimeMillis();
log.info("数据总耗时:" + (end-start) + "ms" );
return "Success";
}
优势:JDBC 中的 PreparedStatement 有预编译功能,预编译之后会缓存起来。
之后SQL执行会比较快,且 JDBC可以开启批处理,这个批处理执行非常给力。
劣势:这种方式插入大量数据时,效率非常底下,不推荐。很多时候我们的 SQL 服务器和应用服务器可能并不是同一台,所以必须要考虑网络 IO。
如果网络 IO 比较费时间的话,那么可能会拖慢 SQL 执行的速度。
💍 方案二:利用mybatis的foreach来实现循环插入(不推荐)
sql
insert into t_bank values (?, ?, ?, ?, ?) , (?, ?, ?, ?, ?) , (?, ?, ?, ?, ?)
java
/**
* 导入银行信息
*
* @param bankList
* @return java.lang.String
*/
@Override
public String importBank(List<TBank> bankList) {
if (StringUtils.isNull(bankList) || bankList.size() == 0)
{
throw new CustomException("导入用户数据不能为空!");
}
long start = System.currentTimeMillis();
tBankMapper.batchInsert(bankList);
long end = System.currentTimeMillis();
log.info("数据总耗时:" + (end-start) + "ms" );
return "Success";
}
xml
<insert id="batchInsert" parameterType="java.util.List">
insert into t_bank (
bank_id,
branch_name,
bank_code,
contact_line,
parent_id,
branch_province,
branch_province_name,
branch_city,
branch_city_name)
values
<foreach collection="list" item="item" separator=",">
(
#{item.bankId},
#{item.branchName},
#{item.bankCode},
#{item.contactLine},
#{item.parentId},
#{item.branchProvince},
#{item.branchProvinceName},
#{item.branchCity},
#{item.branchCityName})
</foreach>
</insert>
优势:不用频繁访问数据库,一条sql搞定,效率比较高。
劣势:一当数据量太大时,会出现拼接的sql语句超长而执行失败,所以当数据量太大时,也不推荐。
二是 SQL 太长了,甚至可能需要分片后批量处理。
三是无法充分发挥 PreparedStatement 预编译的优势,SQL 要重新解析且无法复用
💍 第三种方案,使用sqlSessionFactory实现批量插入(推荐)
java
@Resource
private SqlSessionFactory sqlSessionFactory;
java
/**
* 导入银行信息
*
* @param bankList
* @param isUpdateSupport
* @return java.lang.String
* @author PuWenshuo
* @date 2023/9/18 11:32
*/
@Override
public String importBank(List<TBank> bankList, Boolean isUpdateSupport) {
if (StringUtils.isNull(bankList) || bankList.size() == 0)
{
throw new CustomException("导入用户数据不能为空!");
}
String msg="";
long start = System.currentTimeMillis();
// 指定分页大小
int pageSize = 1000; // 每批插入1000条数据
// 关闭session的自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
try {
TBankMapper bankMapper = sqlSession.getMapper(TBankMapper.class);
// 计算总页数
int totalSize = bankList.size();
int totalPages = (int) Math.ceil((double) totalSize / pageSize);
for (int page = 1; page <= totalPages; page++) {
// 计算当前页的起始和结束索引
int startIndex = (page - 1) * pageSize;
int endIndex = Math.min(startIndex + pageSize, totalSize);
// 获取当前页的数据
List<TBank> banks = bankList.subList(startIndex, endIndex);
// 批量插入数据
tBankMapper.batchInsert(banks);
// 提交事务
sqlSession.commit();
}
msg="恭喜您,数据已全部导入成功!";
} catch (Exception e) {
sqlSession.rollback();
log.error(e.getMessage());
msg="很抱歉,导入失败!";
} finally {
sqlSession.close();
}
long end = System.currentTimeMillis();
log.info("数据总耗时:" + (end-start) + "ms" );
return msg;
}
优势:这种方式可以说是集第一种和第二种方式的优点于一身,既可以提高运行效率,又可以保证大数据量时执行成功,大数据量时推荐使用这种方式。