在MyBatis中批量操作,毋庸置疑离不开foreach
。foreach 的主要用在构建in
条件中,它可以在SQL语句中进行迭代一个集合。
foreach 元素的属性主要有 item,index,collection,open,separator,close。
- item 表示集合中每一个元素进行迭代时的别名;
- index 指 定一个名字,用于表示在迭代过程中,每次迭代到的位置;
- open 表示该语句以什么开始;
- separator 表示在每次进行迭代之间以什么符号作为分隔 符;
- close 表示以什么结束。
在使用 foreach 的时候最关键的也是最容易出错的就是collection 属性。
该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有以下3种情况:
- ① 如果传入的是单参数且参数类型是一个List的时候,collection 属性值为
list
; - ② 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为
array
; - ③ 如果传入的参数是多个的时候,我们就需要把它们封装成一个
Map
; - ④ 自定义
collection
值
【1】mapper中不指定属性值
① 入参为List
即传入的是单参数且参数类型是一个List的时候,collection 属性值为 list
xml实例如下:
<insert id="addTrainRecordBatch" useGeneratedKeys="true" parameterType="java.util.List">
<selectKey resultType="long" keyProperty="id" order="AFTER">
SELECT
LAST_INSERT_ID()
</selectKey>
insert into t_train_record (add_time,emp_id,activity_id,flag)
values
<foreach collection="list" item="item" index="index" separator="," >
(#{item.addTime},#{item.empId},#{item.activityId},#{item.flag})
</foreach>
</insert>
另外一个批量保存应用实例
<!-- 这种方式需要数据库连接属性allowMultiQueries=true;
这种分号分隔多个sql可以用于其他的批量操作(删除,修改)
jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true
-->
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tbl_employee(last_name,email,gender,d_id)
values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
</foreach>
</insert>
另外当使用动态SQL时 ,需要对list进行null 和空判断:
select * from sys_power where 1=1
<if test="powerIdList !=null and powerIdList.size > 0">
and powerId in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
② 入参为array
即,传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
xml实例如下:
<select id="dynamicForeach2" resultType="hashmap">
select * from t_blog where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
③ 入参为map
xml实例如下:
<select id="dynamicForeach3" resultType="hashmap">
select * from t_blog where title like "%"#{title}"%" and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
上述collection的值为ids,是传入的参数Map的key。
【2】mapper中指定属性值
如果有多个参数不想封装为map呢?或者单个参数想自定义属性值呢?这时就可以在mapper中使用注解org.apache.ibatis.annotations.Param
进行指定,然后xml中使用即可。
mapper实例如下
int deleteBatchByIds(@Param("idList") List<Integer> idList);
XML实例如下
<delete id="deleteBatchByIds" parameterType="java.util.List">
delete from tb_sys_moment
WHERE id IN
<foreach collection="idList" open="(" close=")" separator="," item="itemId">
#{itemId}
</foreach>
</delete>
【3】BatchExecutor
严格来讲上述操作并非MyBatis提供的批量操作处理。如下所示,批量插入数据时对应的xml实例。
<insert id="addEmps">
insert into tbl_employee(<include refid="insertColumn"></include>)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
</foreach>
</insert>
其在MyBatis处理过程中会形成如下一条SQL语句:
insert into tbl_employee(
last_name,email,gender,d_id
)
values
(?,?,?,?)
,
(?,?,?,?)
这种方式的批量插入虽然效率高于单次插入*n
但是仍然小于MyBatis提供的BatchExecutor
。
sqlsessionfactory
默认的openSession
() 方法没有参数,它会创建有如下特性的:
- 会开启一个事务(也就是不自动提交)
- 连接对象会从由活动环境配置的数据源实例得到。
- 事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
openSession
方法的ExecutorType
类型的参数,枚举类型:
- ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句。
- ExecutorType.REUSE: 这个执行器类型会复用预处理语句。
- ExecutorType.BATCH: 这个执行器会批量执行所有更新语句
批量操作我们是使用MyBatis
提供的BatchExecutor
进行的,他的底层就是通过jdbc
攒sql
的方式进行的。我们可以让他攒够一定数量后发给数据库一次。
测试实例如下:
public void test01() {
//指定ExecutorType.BATCH创建sqlsession
SqlSession openSession = build.openSession(ExecutorType.BATCH);
UserDao mapper = openSession.getMapper(UserDao.class);
longstart = System.currentTimeMillis();
for(inti = 0; i < 1000000; i++) {
String name = UUID.randomUUID().toString().substring(0, 5);
mapper.addUser(newUser(null, name, 13));
}
openSession.commit();
openSession.close();
longend = System.currentTimeMillis();
System.out.println("耗时时间:"+(end-start));
}
与Spring整合中,推荐额外的配置一个可以专门用来执行批量操作的sqlSession
<bean id="batchSqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
<constructor-arg name="executorType" value="BATCH" ></constructor-arg>
</bean>
需要用到批量操作的时候,我们可以注入配置的这个批量SqlSession
。通过他获取到mapper
映射器进行操作。
需要注意的是:
- 批量操作是在
session.commit()
以后才发送sql语句给数据库进行执行的 - 如果我们想让其提前执行,以方便后续可能的查询操作获取数据,我们可以使用
sqlSession.flushStatements()
方法,让其直接冲刷到数据库进行执行。
三种批量插入措施
- 单次插入
*n
- foreach批量插入
- BatchExecutor
通常根据数据量的不同,建议选择第二种或者第三种。第一种通常不推荐。
参考博文:
Hibernate批量处理数据;
JDBC批量处理数据。