概述
SQL语句中有时候会使用IN关键字,比如 id in (1,2,3,4)。
虽然可以使用${ids}方式直接获取值,但${ids}不能防止SQL注入, 想要避免SQL注入就需要用#{}的方式,这时就要配合使用foreach标签来满足需求.
foreach可以对数组、Map或者实现了Iterable接口(比如List、Set)的对象进行遍历。 数组在处理的时候可以转换为List对象。 因此foreach遍历的对象可以分为两大类
- Iterable类型
- Map类型。
这两种类型在遍历循环时情况是不一样的,我们通过如下3个示例来讲解foreach的用法
foreach实现in集合
foreach实现in集合(或者数组)是最简单和常见的一种情况
1.需求
根据id集合查出所有符合条件的用户
2.UserMapper接口增加接口方法
/** * * * @Title: selectSysUserByIdList * * @Description: 根据用户ID集合查询用户 * * @param ids * @return * * @return: List<SysUser> */ List<SysUser> selectSysUserByIdList(List<Long> ids);
3.UserMapper.xml增加动态SQL
<select id="selectSysUserByIdList" resultType="com.artisan.mybatis.xml.domain.SysUser"> SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE id in <foreach collection="list" item="userId" open="(" close=")" separator="," index="i"> #{userId} </foreach> </select>
foreach的属性
collection 必填,值为要迭代循环的属性名。 情况有很多种
item 变量名,值为从迭代对象中取出的每一个值
index 索引的属性名,在集合数组请鲁昂下为当前索引值,的那个迭代循环的对象是Map类型时,这个值为Map的key(键值)
open 整个循环内容开头的字符串
close 整个循环内容结尾的字符串
separator 每次循环的分隔符
4.单元测试
@Test public void selectSysUserByIdListTest() { logger.info("selectSysUserByIdListTest"); // 获取SqlSession SqlSession sqlSession = getSqlSession(); try { // 获取UserMapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 模拟idList List<Long> idList = new ArrayList<Long>(); idList.add(1L); idList.add(1001L); // 调用接口方法 List<SysUser> userList = userMapper.selectSysUserByIdList(idList); // userList不为空 Assert.assertNotNull(userList); // userList > 0 Assert.assertTrue(userList.size() > 0); // 期望返回2条数据,符合数据库中记录 Assert.assertEquals(2, userList.size()); logger.info(userList); } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); logger.info("sqlSession close successfully "); } }
日志
2018-04-23 01:49:29,686 INFO [main] (BaseMapperTest.java:26) - sessionFactory bulit successfully 2018-04-23 01:49:29,692 INFO [main] (BaseMapperTest.java:29) - reader close successfully 2018-04-23 01:49:29,696 INFO [main] (UserMapperTest.java:729) - selectSysUserByIdListTest 2018-04-23 01:49:30,203 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE id in ( ? , ? ) 2018-04-23 01:49:30,267 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 1(Long), 1001(Long) 2018-04-23 01:49:30,295 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime 2018-04-23 01:49:30,296 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, admin, 123456, admin@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-23 01:49:30,304 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1001, artisan, 123456, test@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-23 01:49:30,305 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 2 2018-04-23 01:49:30,306 INFO [main] (UserMapperTest.java:747) - [SysUser [id=1, userName=admin, userPassword=123456, userEmail=admin@artisan.com, userInfo=管理员用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018], SysUser [id=1001, userName=artisan, userPassword=123456, userEmail=test@artisan.com, userInfo=测试用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018]] 2018-04-23 01:49:30,314 INFO [main] (UserMapperTest.java:752) - sqlSession close successfully
foreach实现批量插入
前提
如果数据库支持批量插入,就可以通过foreach实现。 批量插入是SQL-92新增的特性,目前支持的数据库有DB2、SQL Server 2008+、PostgreSql8.2+、MySQL、SQLite3.7.11+ 以及H2.
语法
insert into tablename(column-a,[column-b,....]) values('value-1a',['value-1b',...]), ('value-2a',['value-2b',...]), ('value-3a',['value-3b',...]), ......
从上述语法部分可以看到,后面是一个值的循环,因此可以通过foreach来实现循环插入。
1.需求
批量插入用户
2.UserMapper接口增加接口方法
/** * * * @Title: insertSysUserList * * @Description: 批量新增用户 * * @param sysUserList * @return * * @return: int */ int insertSysUserList(List<SysUser> sysUserList);
3.UserMapper.xml增加动态SQL
<insert id="insertSysUserList" keyProperty="id" useGeneratedKeys="true"> insert into sys_user( user_name, user_password, user_email, user_info, head_img, create_time) values <foreach collection="list" item="sysUser" separator=","> ( #{sysUser.userName}, #{sysUser.userPassword}, #{sysUser.userEmail}, #{sysUser.userInfo}, #{sysUser.headImg, jdbcType=BLOB}, #{sysUser.createTime, jdbcType=TIMESTAMP} ) </foreach> </insert>
通过item指定了循环变量名后,在引用值的时候使用的是“属性.属性”的方式,如上所示sysUser.userName
。
4.单元测试
@Test public void insertSysUserListTest() { logger.info("insertSysUserListTest"); // 获取SqlSession SqlSession sqlSession = getSqlSession(); try { // 获取UserMapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 模拟userList List<SysUser> userList = new ArrayList<SysUser>(); for (int i = 0; i < 5; i++) { SysUser sysUser = new SysUser(); sysUser.setUserName("artisanTest_" + i); sysUser.setUserPassword("123456_" + i); sysUser.setUserEmail("artisan_" + i + "@artisan.com"); sysUser.setUserInfo("测试用户" + i); // 模拟头像 sysUser.setHeadImg(new byte[] { 1, 2, 3 }); sysUser.setCreateTime(new Date()); // 添加到SysUser userList.add(sysUser); } // 新增用户 ,返回受影响的行数 int result = userMapper.insertSysUserList(userList); // 返回批量的自增主键 配合 keyProperty="id" useGeneratedKeys="true" 这两个属性 for (SysUser sysUser : userList) { logger.info(sysUser.getId()); } // 只插入一条数据 ,期望是5 Assert.assertEquals(5, result); // 重新查询 List<SysUser> sysUserList = userMapper.selectAll(); // 根据数据库之前的2条记录,加上本次新增的5条(虽未提交但还是在一个会话中,所以可以查询的到) Assert.assertNotNull(sysUserList); Assert.assertEquals(7, sysUserList.size()); } catch (Exception e) { e.printStackTrace(); } finally { // 为了保持测试数据的干净,这里选择回滚 // 由于默认的sqlSessionFactory.openSession()是不自动提交的 // 除非显式的commit,否则不会提交到数据库 sqlSession.rollback(); logger.info("为了保持测试数据的干净,这里选择回滚,不写入mysql,请观察日志,回滚完成"); sqlSession.close(); logger.info("sqlSession close successfully "); } }
日志
2018-04-23 15:31:28,500 INFO [main] (BaseMapperTest.java:26) - sessionFactory bulit successfully 2018-04-23 15:31:28,505 INFO [main] (BaseMapperTest.java:29) - reader close successfully 2018-04-23 15:31:28,508 INFO [main] (UserMapperTest.java:761) - insertSysUserListTest 2018-04-23 15:31:29,091 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: insert into sys_user( user_name, user_password, user_email, user_info, head_img, create_time) values ( ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ? ) 2018-04-23 15:31:29,183 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: artisanTest_0(String), 123456_0(String), artisan_0@artisan.com(String), 测试用户0(String), java.io.ByteArrayInputStream@2f0496f0(ByteArrayInputStream), 2018-04-23 15:31:28.526(Timestamp), artisanTest_1(String), 123456_1(String), artisan_1@artisan.com(String), 测试用户1(String), java.io.ByteArrayInputStream@56517ead(ByteArrayInputStream), 2018-04-23 15:31:28.526(Timestamp), artisanTest_2(String), 123456_2(String), artisan_2@artisan.com(String), 测试用户2(String), java.io.ByteArrayInputStream@53bc21(ByteArrayInputStream), 2018-04-23 15:31:28.526(Timestamp), artisanTest_3(String), 123456_3(String), artisan_3@artisan.com(String), 测试用户3(String), java.io.ByteArrayInputStream@79641ab1(ByteArrayInputStream), 2018-04-23 15:31:28.526(Timestamp), artisanTest_4(String), 123456_4(String), artisan_4@artisan.com(String), 测试用户4(String), java.io.ByteArrayInputStream@1b1498ba(ByteArrayInputStream), 2018-04-23 15:31:28.526(Timestamp) 2018-04-23 15:31:29,190 DEBUG [main] (BaseJdbcLogger.java:145) - <== Updates: 5 2018-04-23 15:31:29,191 INFO [main] (UserMapperTest.java:789) - 1032 2018-04-23 15:31:29,191 INFO [main] (UserMapperTest.java:789) - 1033 2018-04-23 15:31:29,191 INFO [main] (UserMapperTest.java:789) - 1034 2018-04-23 15:31:29,192 INFO [main] (UserMapperTest.java:789) - 1035 2018-04-23 15:31:29,192 INFO [main] (UserMapperTest.java:789) - 1036 2018-04-23 15:31:29,196 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: select a.id , a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime from sys_user a 2018-04-23 15:31:29,197 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 2018-04-23 15:31:29,231 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime 2018-04-23 15:31:29,231 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, admin, 123456, admin@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-23 15:31:29,243 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1001, artisan, 123456, test@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-23 15:31:29,247 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1032, artisanTest_0, 123456_0, artisan_0@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-23 15:31:29.0 2018-04-23 15:31:29,248 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1033, artisanTest_1, 123456_1, artisan_1@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-23 15:31:29.0 2018-04-23 15:31:29,249 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1034, artisanTest_2, 123456_2, artisan_2@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-23 15:31:29.0 2018-04-23 15:31:29,250 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1035, artisanTest_3, 123456_3, artisan_3@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-23 15:31:29.0 2018-04-23 15:31:29,251 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1036, artisanTest_4, 123456_4, artisan_4@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-23 15:31:29.0 2018-04-23 15:31:29,251 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 7 2018-04-23 15:31:29,255 INFO [main] (UserMapperTest.java:807) - 为了保持测试数据的干净,这里选择回滚,不写入mysql,请观察日志,回滚完成 2018-04-23 15:31:29,256 INFO [main] (UserMapperTest.java:810) - sqlSession close successfully
foreach实现动态update
这部分我们主要介绍当参数类型是Map的时候,foreach如何实现动态UPDATE
当参数是Map类型的时候,foreach标签的index属性值对应的不是索引值,而是Map中的key, 利用这个key就可以动态实现UPDATE了。
不使用@Param注解指定参数名的情况
1.UserMapper接口
void updateSysUserByMap(Map<String, Object> map);
这里没有使用@Parma注解指定参数名,因而MyBatis在内部的上线文中使用默认值 _parameter 最为该参数的key ,所以xml中也必须使用_parameter。
2.UserMapper.xml动态SQL
<update id="updateSysUserByMap"> update sys_user set <foreach collection="_parameter" item="value" index="key" separator=","> ${key} = #{value} </foreach> where id = #{id} </update>
这里的key作为列名,对应的值作为该列的值,通过foreach将需要更新的字段拼接在SQL语句中。
使用@Param注解指定参数名的情况
1.UserMapper接口
void updateSysUserByMapWithParam(@Param("userMap") Map<String, Object> map);
2.UserMapper.xml动态SQL
<<update id="updateSysUserByMapWithParam"> update sys_user set <foreach collection="userMap" item="value" index="key" separator=","> ${key} = #{value} </foreach> where id = #{userMap.id} </update>
3,单元测试
@Test public void updateSysUserByMapTest() { logger.info("updateSysUserByMapTest"); // 获取SqlSession SqlSession sqlSession = getSqlSession(); try { // 获取UserMapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 模拟Map Map<String, Object> userMap = new HashMap<String, Object>(); // 查询条件,同时也是where后面的更新字段, 必须存在 userMap.put("id", 1L); // 更新其他字段 userMap.put("user_email", "map@artisan.com"); userMap.put("user_name", "ARTISAN_ADMIN"); // 调用接口,更新数据 // userMapper.updateSysUserByMap(userMap); // 或者 userMapper.updateSysUserByMapWithParam(userMap); // 根据当前id 查询用户 SysUser sysUser = userMapper.selectSysUserById(1L); Assert.assertEquals("map@artisan.com", sysUser.getUserEmail()); Assert.assertEquals("ARTISAN_ADMIN", sysUser.getUserName()); } catch (Exception e) { e.printStackTrace(); } finally { // 为了保持测试数据的干净,这里选择回滚 // 由于默认的sqlSessionFactory.openSession()是不自动提交的 // 除非显式的commit,否则不会提交到数据库 sqlSession.rollback(); logger.info("为了保持测试数据的干净,这里选择回滚,不写入mysql,请观察日志,回滚完成"); sqlSession.close(); logger.info("sqlSession close successfully "); } }
2018-04-23 16:27:06,658 INFO [main] (BaseMapperTest.java:26) - sessionFactory bulit successfully 2018-04-23 16:27:06,661 INFO [main] (BaseMapperTest.java:29) - reader close successfully 2018-04-23 16:27:06,664 INFO [main] (UserMapperTest.java:820) - updateSysUserByMapTest 2018-04-23 16:27:07,243 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: update sys_user set id = ? , user_name = ? , user_email = ? where id = ? 2018-04-23 16:27:07,319 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 1(Long), ARTISAN_ADMIN(String), map@artisan.com(String), 1(Long) 2018-04-23 16:27:07,325 DEBUG [main] (BaseJdbcLogger.java:145) - <== Updates: 1 2018-04-23 16:27:07,327 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: select a.id, a.user_name, a.user_password, a.user_email, a.user_info, a.head_img, a.create_time from sys_user a where id = ? 2018-04-23 16:27:07,328 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 1(Long) 2018-04-23 16:27:07,364 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time 2018-04-23 16:27:07,365 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, ARTISAN_ADMIN, 123456, map@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-23 16:27:07,370 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 1 2018-04-23 16:27:07,373 INFO [main] (UserMapperTest.java:852) - 为了保持测试数据的干净,这里选择回滚,不写入mysql,请观察日志,回滚完成 2018-04-23 16:27:07,374 INFO [main] (UserMapperTest.java:855) - sqlSession close successfully