数据库批量入库方常见方式:Java中foreach和xml中使用foreach
两者的区别:
通过Java的foreach循环批量插入:
当我们在Java通过foreach循环插入的时候,是一条一条sql执行然后将事物统一交给spring的事物来管理(@Transactional),当遇到错误时候可以全部回滚。
缺点:每次执行都会消耗网络io以及磁盘io,执行效率很低。
通过xml中使用foreach批量插入:
在xml中使用foreach会遍历生成一个sql语句然后一次发送到数据库,只需要一次网络 io
如下所示:
<foreach collection="list" item="item" separator=";">
insert into user(id, name, phone)
values (#{id}, #{name}, #{phone})
</foreach>
缺点:如果数据量过大则会生成一个很大的sql,会导致io异常
上诉xml还有优化的写法,如下:
insert into user(id, name, phone)
values
<foreach collection="list" item="item" separator=",">
(#{id}, #{name}, #{phone})
</foreach>
两者的区别是在遍历的内容,第一种写法会生成多条单独的插入sql语句(insert into ,,,,,; insert into ....;),第二种是只遍历values后面的内容,使用了insert into .... values 的语法,减少了sql的大小。
除了以上的两种方法之外还可以自己手动实现一个批量插入或修改的工具类(挺好用的)
如下所示:
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.List;
import java.util.function.BiFunction;
/**
* @author light pwd
* @description
* @date 2024/10/25
*/
@Component
public class MyBatchUtils {
private static final Logger LOG = LoggerFactory.getLogger(MyBatchUtils.class);
/**
* 每次处理1000条
*/
private static final int BATCH_SIZE = 1000;
@Autowired
private SqlSessionFactory sqlSessionFactory;
/**
* 批量处理修改或者插入
* 变成一条一条的数据,然后最后一起执行。并不是 insertBatch那种方式
* @param data 需要被处理的数据
* @param mapperClass Mybatis的Mapper类
* @param function 自定义处理逻辑
* @return int 影响的总行数
*/
public <T, U, R> int batchUpdateOrInsert(List<T> data, Class<U> mapperClass, BiFunction<T, U, R> function) {
int i = 1;
SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
U mapper = batchSqlSession.getMapper(mapperClass);
int size = data.size();
for (T element : data) {
function.apply(element, mapper);
if (i % BATCH_SIZE == 0 || i == size) {
batchSqlSession.flushStatements();
}
i++;
}
// 非事务环境下强制commit,事务情况下该commit相当于无效(交给spring的事物来管理)
batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive());
} catch (Exception e) {
batchSqlSession.rollback();
LOG.error(e.getMessage());
throw BusinessException.of(CommonErrorCodes.RUN_TIME_EXCEPTION, "MybatisBatchUtilsException");
} finally {
batchSqlSession.close();
}
return i - 1;
}
}
该方法既结合了Java种foreach的优势,又结合了在xml种foreach的优势。这种方式与mybatis-plus的insertBatch是不同的,mybatis-plus默认提供的insertBatch方法本质是一条一条sql执行然后一起提交。
该方法对于大数据量会自动分批插入,每次1000条的插入到数据库,省去了分批处理。
用法也很简单:
batchUtils.batchUpdateOrInsert(haveIdColumn, PreColumnConfigMapper.class,
(item, mapper) -> mapper.updateOne(item, createTime, createrId)
);
batchUtils.batchUpdateOrInsert(noIdLine, PreLineConfigMapper.class,
(item, mapper) -> mapper.insertOne(item, createTime, createrId)
);
其中batchUtils就是通过spring注入进来的MyBatchUtils的bean,haveIdColumn/noIdLine是一个待插入的List数据,PreLineConfigMapper/PreColumnConfigMapper都是mapper文件,
他们得updateOne和insertOne xml方法如下所示(我是pg数据库,mysql是类似的):
<update id="updateOne" parameterType="com.jiuaoedu.serviceeducation.pre.pojo.PreColumnConfig">
update service_education.pre_column_config
set type = #{bean.type,jdbcType=VARCHAR}, grade = #{bean.grade,jdbcType=VARCHAR}, subject = #{bean.subject,jdbcType=VARCHAR},
name = #{bean.name,jdbcType=VARCHAR}, name_id = #{bean.nameId,jdbcType=VARCHAR},order_num = #{bean.orderNum,jdbcType=INTEGER},
creater_id = #{createrId,jdbcType=BIGINT},create_time = #{createTime,jdbcType=TIMESTAMP}, target_num = null, class_num = null, del = 1
where column_id = #{bean.columnId,jdbcType=BIGINT}
</update>
<insert id="insertOne" parameterType="com.jiuaoedu.serviceeducation.pre.pojo.PreLineConfig">
insert into service_education.pre_line_config
(
plan_id, group_id, title,type, start_date, end_date, start_time, end_time, week, order_num,
count_date,year,term, time_order, create_time, creater_id
)
values
(
#{bean.planId,jdbcType=BIGINT},#{bean.groupId,jdbcType=VARCHAR}, #{bean.title,jdbcType=VARCHAR},#{bean.type,jdbcType=VARCHAR},
#{bean.startDate,jdbcType=TIMESTAMP}, #{bean.endDate,jdbcType=TIMESTAMP},
#{bean.startTime,jdbcType=TIME},#{bean.endTime,jdbcType=TIME},#{bean.week,jdbcType=VARCHAR},
#{bean.orderNum,jdbcType=INTEGER}, #{bean.countDate,jdbcType=TIMESTAMP},#{bean.year,jdbcType=INTEGER},
#{bean.term,jdbcType=VARCHAR}, #{bean.timeOrder,jdbcType=INTEGER},#{createTime,jdbcType=TIMESTAMP}, #{createrId,jdbcType=BIGINT}
)
</insert>
注意事项:当在有事物的方法A中使用MyBatchUtils 工具类的时候,工具类的事物是跟方法A同步的,即方法A回滚则执行的所有数据都会回滚
当在没有事物的方法A中使用时:因为MyBatchUtils 会分批次插入所以每批次会有一个事物,提交也是一个批次一起提交,回滚也只回滚一个批次
奋斗不止,进步无止境,让人生在追求中焕发光彩!!!