1. 为什么使用单个语句进行批量增删改查操作?
在MyBatis中,批量操作数据时,直接在for循环中逐条执行SQL语句与使用单个语句进行批量操作有显著区别,主要体现在性能、资源消耗和执行效率上:
性能与效率
- 逐条操作:如果在服务层(如Java的Service类中)使用for循环,对每条数据都单独执行一次SQL操作,这意味着每次操作都会涉及数据库连接的建立、SQL语句的发送、执行和结果处理,随后关闭连接。这种方式会导致大量重复的数据库交互,极大地增加了网络IO负担和数据库负载,性能低下。
- 批量操作:相比之下,使用MyBatis的<foreach>标签或者直接在SQL中利用IN语句等方式进行批量操作,可以在一次数据库交互中处理多条数据。这样可以显著减少数据库连接的建立和释放次数,降低网络通信成本,提高执行效率。
资源消耗
- 逐条操作:由于每次操作都需要一个新的数据库连接,因此资源消耗较大,尤其是在高并发场景下,可能会迅速耗尽数据库连接池资源。
- 批量操作:批量操作通过一次性处理多条记录,减少了数据库连接的需求,从而降低了资源消耗,有助于保持系统稳定性。
事务管理
- 逐条操作:如果需要事务支持,每次操作都可能需要单独管理事务,这可能导致事务开销增大,并且在出现错误时回滚策略复杂。
- 批量操作:批量操作可以在一个事务中完成,简化了事务管理,提高了数据一致性。一旦发生错误,可以整体回滚,保证数据的完整性。
数据安全性与一致性:
- 批量操作虽然高效,但在处理大量数据时需谨慎,因为一旦发生错误,可能影响到整个批次的数据,而不是单条数据。逐条操作在这方面更为安全,但牺牲了效率。
2. <foreach> 标签
2.1. 简介
MyBatis中的<foreach>
标签主要用于在动态SQL中迭代集合,实现批量操作或者构建含有多个变量的IN条件语句等功能。
2.2. 基本语法与属性
<foreach collection="collectionExpression" item="itemAlias" index="index" open="startString" close="endString" separator="separatorString">
SQL片段
</foreach>
collection="collectionExpression"
:指定要遍历的集合对象的表达式,例如:list
、array
等。item="itemAlias"
:表示集合中每一个元素的别名,用于在<foreach>
内部引用当前遍历的元素。index="index"(可选)
:在集合或数组迭代时,当前索引值将被赋值给index
变量,可以在SQL语句中使用${index}
来引用它。open="startString"(可选)
:在遍历结果之前添加的字符串,通常用于SQL的开始部分,比如(
。close="endString"(可选)
:在遍历结果之后添加的字符串,通常用于SQL的结束部分,比如)
。separator="separatorString"(可选)
:分隔符,用于分隔每个遍历元素产生的SQL片段,默认是逗号,
。
2.3. 示例
2.3.1. 传入含有指定分割符的字符串String
<!-- 传入String devices="1,2,3,4,5" -->
<!-- 使用split(',')将其转换为List, item="devcie"指定分割后的元素别名 -->
<foreach collection="devices.split(',')" item="device" >
#{device}
</foreach>
2.3.2. 传入数组T[]
<!-- 传入String[] devices={"1", "2", "3", "4", "5"} -->
<!-- List可以直接遍历 -->
<foreach collection="devices" item="device" >
#{device}
</foreach>
2.3.3. 传入Map或实体类中包含列表或指定分割符的字符串String
<!--
Map<T,T> map = {
"ids": [1,2],
"devices":"1,2,3,4,5"
}
-->
<select>
<foreach collection="devices.split(',')" item="device" >
#{device}
</foreach>
<foreach collection="ids" item="id" >
#{id}
</foreach>
</select>
2.3.4. 传入列表List<基本类型>
<!-- List<String> devices = Arrays.asList("1", "2", "3", "4", "5"); -->
<!-- List可以直接遍历 -->
<foreach collection="devices" item="device" >
#{device}
</foreach>
2.3.5. 传入列表List<Map或实体类>
<!--
此处便于理解写成JSON格式
List<Map<T,T>> deviceList = [{
"id": 1,
"name":"一号设备"
},{
"id": 2,
"name":"二号设备"
},{
"id": 3,
"name":"三号设备"
}]
-->
<update id="updateByListMaps" parameterType="java.util.List">
<foreach collection="deviceList" item="deviceMap" separator=";">
UPDATE table_name
SET column_name = #{deviceMap.name}
WHERE id = #{deviceMap.id}
</foreach>
</update>
3. 批量插入
<!--
// Dao层java代码
Integer addRecordList(List<Record> crList);
-->
<insert id="addRecordList">
INSERT INTO
record(id, code, state, start_time, create_time)
VALUES
<foreach collection="crList" separator=", "item="item">
(#{item.id},#{item.code},#{item.state},#{item.start_time},#{item.create_time})
</foreach>
</insert>
4. 批量删除
<!--
// Dao层java代码
Integer delChannelInfoByIds(String ids);
-->
<delete id="delChannelInfoByIds">
delete from
channel
where id in
<foreach collection="ids.split(',')" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
5. 批量更新
<!--
// Dao层java代码
Integer updateChannelById(List<Map<String,Object>> updateList);
-->
<update id="updateChannelById">
<foreach collection="updateList" item="item" separator=";">
update
channel
set
number=#{item.channelNumber},
name=#{item.channelName}
where
id=#{item.deviceId}
</foreach>
</update>
6. 批量查询
<!--
// Dao层java代码
List<Point> queryPointByIds(String ids);
-->
<select id="queryPointByIds" resultMap="BaseResultMap">
select
i.*
from
point i
where
i.id in
<foreach item="item" collection="ids" open="(" separator=", close=")">
#{item}
</foreach>
</select>