项目开发中在很多地方可能会遇到同时插入多条记录到数据库的业务场景,如果业务级别循环单条插入数据会不断建立连接且有多个事务,这个时候如果业务的事务执行频率相当较高的话(高并发),对数据库的性能影响是比较大的;为了提高效率,批量操作会是不错的选择,一次批量操作只需要建立一次连接且一个事务,能很大程度上提高数据库的效率。
批量插入操作的sql语句原型如下:
1. insert into
2. wsjiang_test(col1, col2, col3)
3. values
4. (col1_v, col2_v, col3_v),
5. (col1_v, col2_v, col3_v),
6. ...
这里我们以ibatis为例,进行应用说明!
一、 ibatis iterate标签配置说明
1. < iterate
2. property ="" /*可选,
3. 从传入的参数集合中使用属性名去获取值,
4. 这个必须是一个List类型,
5. 否则会出现OutofRangeException,
6. 通常是参数使用时才使用,
7. 如果传入的参数本身是一个, 不能只用这个属性.
8. 不知道为啥官网: /docs/dotnet/datamapper/#id386679
9. 说这个属性是必须的, 但是测试的时候是可以不设置这个属性的, 还望那位大虾知道, 讲解一下.
10. */
11. conjunction ="" /*可选,
12. iterate可以看作是一个循环,
13. 这个属性指定每一次循环结束后添加的符号,
14. 比如使每次循环是OR的, 则设置这个属性为OR*/
15. open ="" /*可选, 循环的开始符号*/
16. close ="" /*可选, 循环的结束符号*/
17. prepend ="" /*可选, 加在open指定的符号之前的符号*/
18. > </ iterate >
二、 ibatis iterate标签使用示例
1、批量查询
1. < select id ="iterate_query" parameterClass ="" >
2. <![CDATA[
3. selelct * fromwsjiang_test where id=1
4. ]]>
5. < iterate prepend ="prepend" conjunction ="conn" open ="open" colse ="close" >
6. /*使用作为参数不能设置property属性*/
7. <![CDATA[
8. #v[]#
9. ]]> /*这里的"[]"是必须的, 要不然ibatis会把v直接解析为一个String*/
10. </ iterate >
11. </ select >
如果传入一个List为[123,234,345], 上面的配置将得到一个sql语句:
select * fromwsjiang_test where id=1 prepend open 123 conn 234 conn 345 close
2、批量插入
A、不使用open/close属性
1. < insert id =" iterate_insert1 " parameterClass ="" >
2. <![CDATA[
3. insert intowsjinag_test( col1 , col2 , col3 ) values
4. ]]>
5. < iterate conjunction ="," >
6. <![CDATA[
7. (#test[]. col1 #, # test []. col2 #, # test []. col3 #)
8. ]]>
9. </ iterate >
10. </ insert >
上面的配置将得到一个sql语句:
insert into wsjiang_test( col1, col2, col3 ) values (?, ?, ?) , (?, ?, ?) , (?, ?, ?)
B、使用open/close属性
1. < insert id ="betchAddNewActiveCode" parameterClass ="" >
2. <![CDATA[
3. insert intowsjinag_test( col1 , col2 , col3 ) values
4. ]]>
5. < iterate conjunction ="," open ="(" close =")" >
6. <![CDATA[
7. /*这里不加"("和")"*/
8. #test[]. col1 #, # test []. col2 #, # test []. col3 #
9. ]]>
10. </ iterate >
11. </ insert >
上面的配置将得到一个sql语句:
insert into wsjiang_test( col1, col2, col3 ) values (?, ?, ? , ?, ?, ? , ?, ?, ?)
这两种使用方式区别是相当大的. conjunction,open 和close这几个属性需小心使用,将其区分开.
三、单条插入返回新增记录主键
通常情况,ibatis的insert方法需要返回新增记录的主键,但并非任何表的insert操作都会返回主键(这是一个陷阱);要返回这个新增记录的主键,前提是表的主键是自增型的,或者是Sequence的;且必须启用ibatis的selectKey 标签; 否则获取新增记录主键的值为0或者null。
ibatis的配置:
1. < insert id =" iterate_insert1 " parameterClass ="Object" >
2. <![CDATA[
3. insert intowsjinag_test( col1 , col2 , col3 )
4. values (# col1 #, # col2 #, # col3 #)
5. ]]>
6. < selectKey keyProperty ="id" resultClass= "Long" >
7. <![CDATA[
8. SELECT LAST_INSERT_ID() AS value
9. ]]>
10. </ selectKey >
11. </ insert >
四、 插入返回 新增记录数
在第三节中已经讲清楚通过ibatis的insert方法只能得到新增记录的ID; 如果对于无需知道新增记录ID,只需要知道有没有插入成功的业务场景时,特别是对于批量插入,配置的selectKey 可能会有问题时,一次插入多条,拿不到新增的ID,这时我们就只能返回插入成功的记录数来区分是否新增成功!但是insert方法是不会返回记录数;于是我们可以使用ibatis的update方法来调用没有配置 selectKey 标签的insert语句,这样就能返回影响(插入)的记录数了!
某些地方理解不是很深刻,还请不吝赐教!
比如,经常遇到的情况是,要把表A中的符合条件的数据全部取出来,形成一个结果集,
然后针对结果集中的每一条数据,循环插入到数据表B中去,除了传统的在JAVA代码中
FOR循环来做外,在ibatis 2中,还增加了iterate标签,可以用来生成循环要执行的语句,介绍如下:
<iterate
property=""/*可选,
从传入的参数集合中使用属性名去获取值,
这个必须是一个List类型,
否则会出现OutofRangeException,
通常是参数使用时才使用,
如果传入的参数本身是一个,不能只用这个属性.
conjunction=""/*可选,
iterate可以看作是一个循环,
这个属性指定每一次循环结束后添加的符号,
比如使每次循环是OR的,则设置这个属性为OR*/
open=""/*可选,循环的开始符号*/
close=""/*可选,循环的结束符号*/
prepend=""/*可选,加在open指定的符号之前的符号*/
>
</iterate>
例子:
<iterate prepend=”AND”property=”userNameList” open=”(” close=”)” conjunction=”OR”>
username=#userNameList[]#
</iterate>
这个时候,会生成如下形式的语句:
(username=xxx1 or username=xxx2 orusername=xxx3)
因为open,close中指定了括号,conjunction则指定了分割是用or,
又如:
id in
<iterate prepend=""property="ids" open="(" close=")" conjunction="," >
#ids[]#
</iterate>
生成为:
id in (xx1,xx2,xx3,.....),
注意:使用<iterate>时,在List元素名后面包括方括号[]非常重要,方括号[]将对象标记为List,
以防解析器简单地将List输出成String。
再来看几个例子:
1) 删除的:
<delete>
delete from t_tablewhere key in
<iterateproperty="keys" conjunction="," open="("close=")">
#keys[]#
</iterate>
</delete>
2 ) update:
如果传用的参数是自己定义的类,如:
<update parameterClass="">
UPDATE TBL_COMPANY
SET ADDRESS = #address#
WHERE COMPANY_ID IN
<iterateconjunction="," open="(" close=")"property="companyIds" >
#companyIds[]#
</iterate>
</update>
生成Sql语句:UPDATE TBL_COMPANY SET ADDRESS='address' WHERE COMPANY_ID IN ( 45, 50,70)
其中:companyIds是类中的list属性。
3) 批量插入,oracle中可以用insertall into,比如:
<insert parameterClass="map">
insert all
<iterate conjunction=" "property="ragrouppreinsert">
into GROUP_ASSETRA
(RISKID, GROUPID,GROUPNAME,AV,RALISTID)
values
(
F_GETSEQ(),#ragrouppreinsert[].groupId#,#ragrouppreinsert[].groupname#,#ragrouppreinsert[].av#,
#ralistId#
)
</iterate>
select * fromdual
</insert>
注意:比如这里parameterClass="map"中,表明传入的是一个MAP,这个MAP中
可以包含内容很丰富的东西了,比如:
("ragrouppreinsert",list) //这里ragrouppreinsert"存放的是一个LIST结果集了
("ralistId",ralistId) //这里另外设置一个要批量增加到group_assetra表中的数据
注意在上面的语句中,因为要将结果集合list中的每一条记录对应的字段插入到
GROUP_ASSETRA中,所以要使用#ragrouppreinsert[].groupId#的写法,
而把map中的ralistId插进去,则只需要#ralistId#就可以了,这样其实就是
插入到group_assetra表中的每一个ralistid字段都是相同的值了
F_GETSEQ()是一个函数,获得GROUP_ASSETRA表中的每次的序列,
具体其中遇到的问题,参考不错的一篇讲oracleinsert all into的文:
/post/468/188133
<!-- 批量添加消息 -->
<insert parameterClass="">
Insert into messages
<iterate property="statusList"conjunction=" UNION ALL ">
SELECT (SELECT rawtohex(sys_guid()) AS messageId FROMDUAL),#statusList[]#,0,0,#link#,#title#,#content#,#createDate# from dual
</iterate>
</insert>