1.0 两种插入方式
mybaties 中向数据库中插入批量插入数据 ,有两种方法。一种是使用mybaties的批量执行器模式。一种使用sql语句拼接的模式插入。
2.0 使用方式
2.1 oracle sql拼接的方式如下:
<select id="newId" resultType="java.lang.Long" useCache="false" flushCache="true">
select SEQ_INSPECTION_WCS_DETAIL.nextval
from dual
</select>
2.1 oracle主键获取
oracle使用这种拼接的方法主键ID 不能自动生成 ,得使用oracle中的直增序列来实现 , 实现方式提供两种 1,在mapper 中创建一个序列获取方法,然后在java调用该方法来创建实列的id 如下 ,注意要设置 useCache="false" flushCache="true" 不然会一直获取到相同的序列
<select id="newId" resultType="java.lang.Long" useCache="false" flushCache="true"> select SEQ_INSPECTION_WCS_DETAIL.nextval from dual </select>
调用方法设置主键id 然后插入数据 :
ImqStandardFaiWcsDetailMapper imqStandardFaiWcsDetailMapper; public void create(List<ImqStandardFaiWcsDetail> list ){ List<ImqStandardFaiWcsDetail> insertData = list.stream().peek(imqStandardFaiWcsDetail -> { Long id = imqStandardFaiWcsDetailMapper.newId(); imqStandardFaiWcsDetail.setId(id); }).collect(Collectors.toList()); imqStandardFaiWcsDetailMapper.batchInsert(insertData); }
这种方式在数据特别大的使用会出现异常,比如10万条数据 那么就要获取ID 10w次,和数据库交互10次,消耗了大量的时间。还有一次拼接太多的数据那么网络io将面临问题,超过的一定的界限值,效率直接下降,这种情况,一般的对象,大概每次insert200 条是最好的,那么怎么来解决这个问题?
2.2 使用触发器来创建ID
创建出发器:
create or replace trigger t1id_tr1 before insert on INSPECTION_WCS_DETAIL for each row begin select SEQ_INSPECTION_WCS_DETAIL.currval into :new.ID from dual; end t1id_tr1;
在mapper中就不用写ID这个字段的代码了,在java代码中直接调用
<insert id="batchInsert" parameterType="list" useGeneratedKeys="false"> <!--@mbg.generated--> insert all <foreach collection="list" item="item"> into IMQ_STANDARD_FAI_WCS_DETAIL (ID, STANDARD_FAI_ML_ID, WCS_RELATED, WCS_NAME, WCS_DIRECTION, WCS_VARIABLE, WCS_COEFFICIENT,PART_ID ) values ( #{item.id,jdbcType=DECIMAL}, #{item.standardFaiMlId,jdbcType=DECIMAL}, #{item.wcsRelated,jdbcType=VARCHAR}, #{item.wcsName,jdbcType=VARCHAR}, #{item.wcsDirection,jdbcType=CHAR}, #{item.wcsVariable,jdbcType=VARCHAR}, #{item.wcsCoefficient,jdbcType=VARCHAR}, #{item.partId,jdbcType=DECIMAL} ) </foreach> select 1 from dual </insert>
但是问题又来了 ,如果我需要再插入后获取到实列的ID改怎么办?这种方法是没法返回ID 的,而且还没有解决一次插入过多数据引起的sql语句过长,造成网络阻塞。
2.3 创建 ExecutorType.BATCH 来执行,使用手动设置ID
同样编写创建id 的序列方法
<select id="newId" resultType="java.lang.Long" useCache="false" flushCache="true"> select SEQ_INSPECTION_WCS_DETAIL.nextval from dual </select>
创建insert的statement
<insert id="insertSelective" parameterType="list" useGeneratedKeys="false">
insert into IMQ_STANDARD_FAI_WCS_DETAIL
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
ID,
</if>
......省略字段
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=DECIMAL},
</if>
....省略字段
</trim>
</insert>
创建java 调用代码的工具类
package com.ewpt.common.utils; import com.ewpt.common.utils.uuid.OracleSeq; import com.ewpt.framework.web.domain.BaseEntity; import com.ewpt.framework.web.domain.PrimaryKeyId; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.Collection; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Supplier; /** * @author CLPS */ @Component public class MybatisBatchUtils { /** * 每次处理1000条 */ private static final int BATCH_SIZE = 1000; private static SqlSessionFactory sqlSessionFactory; @Autowired public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { MybatisBatchUtils.sqlSessionFactory = sqlSessionFactory; } /** * 批量处理修改或者插入 * * @param data 需要被处理的数据 * @param mapperClass Mybatis的Mapper类 * @param function 自定义处理逻辑 * @return int 影响的总行数 */ public static <T extends PrimaryKeyId,U,R> int batchUpdateOrInsert(Collection<T> data, Class<U> mapperClass, BiFunction<T,U,R> function, Supplier<OracleSeq> seqSupplier) { int i = 1; SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); try { U mapper = batchSqlSession.getMapper(mapperClass); OracleSeq oracleSeq = seqSupplier.get(); int size = data.size(); for (T element : data) { Long id = oracleSeq.getId(); element.setKeyId(id); if (Objects.equals(id,oracleSeq.getNextVal())){ oracleSeq = seqSupplier.get(); } function.apply(element,mapper); if ((i % BATCH_SIZE == 0) || i == size) { batchSqlSession.flushStatements(); } i++; } // 非事务环境下强制commit,事务情况下该commit相当于无效 batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive()); } catch (Exception e) { batchSqlSession.rollback(); throw new RuntimeException(e); } finally { batchSqlSession.close(); } return i - 1; } public static <T extends BaseEntity,U,R> int batchUpdateOrInsert(Collection<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相当于无效 batchSqlSession.commit(!TransactionSynchronizationManager.isSynchronizationActive()); } catch (Exception e) { batchSqlSession.rollback(); throw new RuntimeException(e); } finally { batchSqlSession.close(); } return i - 1; } }
java调用代码 :
MybatisBatchUtils.batchUpdateOrInsert(InsertData,ImqStandardFaiWcsDetailMapper.class,(item, imqStandardFaiWcsDetailMapper) -> { imqStandardFaiWcsDetailMapper.insertSelective(item); return 1; });
这种方式插入数据,效率将大大的提高 ,这特是在大数据下有明显的差异
3. mysql 拼接方式如下
<insert id="batchRoleDept" useGeneratedKeys="false"> insert into sys_role_dept(id, dept_id) <foreach item="item" index="index" collection="list"> values (#{item.id},#{item.deptId}) </foreach> </insert>
mysql 的插入拼接比较的简单,在不考虑性能的情况下,可以设置id自增,自动生成主键 ,返回主键ID 。在数据量打的情况使用ExecutorType.BATCH模式也是最佳的选择