概述

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