MyBatis foreach 标签常用方法总结

Posted 长安明月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis foreach 标签常用方法总结相关的知识,希望对你有一定的参考价值。

一、前言

  在 MyBatis 中,常常会遇到集合类型的参数,虽然我们可以通过 OGNL 表达式来访问集合的某一个元素,但是 OGNL 表达式无法遍历集合。foreach 标签就是专门用来解决这类问题的,foreach 标签可以用来遍历数组、列表和 Map 等集合参数,实现批量操作或一些简单 SQL 操作。

二、foreach 元素属性简介

  foreach 元素的属性主要有 item,index,open,separator,close,collection。各属性含义如下所示。

2.1 item

  集合中元素迭代时的别名,该参数为必选。

2.2 index

  在 list 和数组中,index 是元素的序号;在 map 中,index 是元素的 key。该参数可选。

2.3 open

  foreach 代码的开始符号,一般是 ”(“,和 close=“)” 合用。常用在 in(),values() 时。该参数可选。

2.4 separator

  元素之间的分隔符,例如在 in() 的时候,separator=“,” 会自动在元素中间用 “,“ 隔开,避免手动输入逗号导致 SQL 错误,如 in(1, 2,) 这样。该参数可选。

2.5 close

  foreach 代码的关闭符号,一般是 ”)“,和 open=“(” 合用。常用在 in(),values()时。该参数可选。

2.6 collection

  要被 foreach 标签循环解析的对象。

  foreach 标签的 collection 属性在接受参数名时,有两种情况:

  • 匿名参数
    当在 java 方法中没有通过 @Param 注解指定参数名时,列表类型默认参数名为 ”list“,数组类型默认参数名为 ”array“,Map 对象没有默认值。

  • 具名参数
    java 方法中使用了 @Param 注解指定了参数名称,则 foreach 中的 collection 属性必须为参数名。

  在作为入参时可以使用 @Param(“keyName”) 来设置该键值,设置 keyName 后,list、array 将会失效。除了入参这种情况外,还有一种是作为参数对象的某个字段,例子如下。如果 User 有属性 List ids。入参是 User 对象,那么这个collection = “ids”。如果 User 有属性 Ids ids,其中 Ids 是个对象,Ids 有个属性 List id,入参是 User 对象,那么 collection = “ids.id”。

  注意点:在使用 foreach 的时候,最关键的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况(未通过 @Param 指定别名时)。

  • 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 ”list“。
  • 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 ”array“。
  • 如果传入的参数是多个的时候,我们可以把它们封装成一个 Map,当然单参数也可以封装成 map。实际上如果你在传入参数的时候,在 MyBatis 里面也是会把它封装成一个 Map 的,map 的 key 就是参数名,所以这个时候 collection 属性值就是传入的 List 或 array 对象在自己封装的 map 里面的 key。
三、# 与 $ 的区别

  在使用参数的过程中,会遇到 # 与 $ 的问题,因此简单总结下两者之间的区别。

  1. $param 传递的参数会被当成 SQL 语句中的一部分,比如传递表名,字段名,字段类型等数据。
    例如,传入值为 id,order by $param 则解析成 SQL:order by id。

  2. #parm 传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
    例如,传入值为 id,select * from table where name = #param 则解析成 SQL:select * from table where name = ? (其中问号在执行时传入值 “id”)。

  为了安全,能用 # 的地方就用 # 方式传参,这样可以有效的防止 SQL 注入攻击。

  官方说明:mybatis 在处理 # 时,会将 SQL 中的 # 替换为 “?”,调用 PreparedStatement 的 set 方法来赋值;mybatis 在处理 $ 时,就是把 $ 替换成变量的值。使用 # 可以有效的防止 SQL 注入,提高系统安全性。

四、实例实战

  本文以如下几个例子简单总结下 foreach 是如何遍历列表、数组和 Map 的。

4.1 遍历 List<String> 列表

  Java 层接口

List<Rule> selectRulesByList(List<String> ids);

  XML

  <select id="selectRulesByList" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List" />
    FROM tbl_test_rule WHERE rule_id IN
    <foreach collection="list" open="(" close=")" separator="," item="item" index="index">
      #item
    </foreach>
  </select>

  运行时 SQL 语句

==>  Preparing: SELECT rule_id, rule_name, rule_type FROM tbl_test_rule WHERE rule_id IN ( ? , ? )
==> Parameters: 10001(String), 20002(String)
<==      Total: 2
4.2 遍历 List<Object> 列表

  项目中定义了一个实体类 Rule,在批量插入时需要遍历 List<Rule>,实现方式见下文。

  Java 层接口

int insertRules(@Param("rules") List<Rule> rules);

  XML

  <insert id="insertRules">
    INSERT INTO tbl_test_rule (rule_id, rule_name, rule_type) VALUES
    <foreach collection="rules" separator="," item="rule">
      (#rule.ruleId, #rule.ruleName, #rule.ruleType)
    </foreach>
  </insert>

  运行时 SQL 语句

==>  Preparing: INSERT INTO tbl_test_rule (rule_id, rule_name, rule_type) VALUES (?, ?, ?) , (?, ?, ?)
==> Parameters: ruleId1(String), ruleName1(String), 1(String), ruleId2(String), ruleName2(String), 2(String)
<==    Updates: 2
4.3 遍历数组

  Java 层接口

List<Rule> selectRulesByArray(String[] ids);

  如果 ids 参数使用 @Param 注解指定了参数名称,则 foreach 标签中的 collection 属性必须为该名称;但若未指定名称,则在 foreach 标签中使用默认数组名称 array,如下所示

  XML

  <select id="selectRulesByArray" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List" />
    FROM tbl_test_rule WHERE rule_id IN
    <foreach collection="array" open="(" close=")" separator="," item="item" index="index">
      #item
    </foreach>
  </select>

  运行时 SQL 语句

==>  Preparing: SELECT rule_id, rule_name, rule_type FROM tbl_test_rule WHERE rule_id IN ( ? , ? )
==> Parameters: 10001(String), 20002(String)
<==      Total: 2
4.4 遍历 Map 实现 insert into … on duplicate key update

  数据库表 tbl_test_discount,联合主键(cert_no, rule_id, cycle_id),存储了不同用户不同周期下的折扣金额。请求参数 Map 中存储了某一用户不同规则(key 为不同的 rule_id 值)和各个规则下的折扣值(value 为 dis_sum),如下方 Java 接口定义。需要实现:当数据库中无主键记录时,将记录插入数据库;如数据库中存在主键记录时,更新折扣值,将折扣值累加计算(即实现 insert into … on duplicate key update 操作)。过程示例如下。

  Java 层接口

    int saveOrUpd(@Param(value = "certNo") String certNo,
                  @Param(value = "cycleId") String cycleId,
                  @Param(value = "params") Map map);

  使用 foreach 标签遍历 Map 时,collection 属性值为 @param 注解指定的参数名,即 params,且 item 是 Map 的键值,index 是键名。

  XML

  <update id="saveOrUpd" parameterType="java.util.Map">
    <foreach collection="params" index="key" item="value">
      insert into tbl_test_discount (cert_no, rule_id, cycle_id, dis_sum) values
      (#certNo, #key, #cycleId, #value) on duplicate key update dis_sum = dis_sum + #value;
    </foreach>
  </update>

  mybatis 设置允许批量更新

  mybatis 会根据上述 XML 文件的配置,动态生成多条 SQL。要让 mybatis 成功执行多条语句,须开启允许批量查询设置,即在 jdbc-url 连接信息中添加 &allowMultiQueries=true,如下所示。

spring.datasource.jdbc-url=jdbc:mysql://localhost:3306/testdb?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true

  MySQL 连接数据库时,添加语句 &allowMultiQueries=true 的作用:可以在 SQL 语句后携带分号,实现多语句执行;可以执行批处理,同时发出多个 SQL 语句。

  运行时 SQL 语句

==>  Preparing: insert into tbl_test_discount (cert_no, rule_id, cycle_id, dis_sum) values (?, ?, ?, ?) on duplicate key update dis_sum = dis_sum + ?; insert into tbl_test_discount (cert_no, rule_id, cycle_id, dis_sum) values (?, ?, ?, ?) on duplicate key update dis_sum = dis_sum + ?; insert into tbl_test_discount (cert_no, rule_id, cycle_id, dis_sum) values (?, ?, ?, ?) on duplicate key update dis_sum = dis_sum + ?; insert into tbl_test_discount (cert_no, rule_id, cycle_id, dis_sum) values (?, ?, ?, ?) on duplicate key update dis_sum = dis_sum + ?;
==> Parameters: testCertNo1(String), 30001(String), 202212(String), 225(Integer), 225(Integer), testCertNo2(String), 20002(String), 202212(String), 385(Integer), 385(Integer), testCertNo3(String), 20001(String), 202212(String), 553(Integer), 553(Integer), testCertNo4(String), 10001(String), 202212(String), 300(Integer), 300(Integer)
<==    Updates: 1

  foreach 标签是使用非常广泛的一个标签,当使用 SQL 进行批量插入、查询时都可能使用到它。列表遍历的使用最为广泛,数组和 Map 则相对较少。

文章参考

Mybatis常用功能总结

目录

获取参数值

@Param取别名

以类作为参数类型

获取插入的数据自增的主键

字段名和属性名建立映射

字段名和属性名建立一对一映射

字段名和属性名建立多对一映射

级联方式处理映射关系

 association处理映射关系

 分步查询处理映射关系

字段名和属性名建立一对多映射

collection处理映射关系

  分步查询处理映射关系

动态SQL

if标签

trim标签

choose、when、otherwise标签

foreach标签

sql标签

 分页插件


获取参数值

@Param取别名

可以通过@Param注解标识mapper接口中的方法参数

方法名 

User getUserByEmail(@Param(value = "email") String email);

sql代码 

<!--    User getUserByEmail(@Param(value = "email") String email);-->
<select id="getUserByEmail" resultType="User">
    select id,username,password
    from t_user
    where userEmail = #email
</select>

以类作为参数类型

 以User类为参数,sql代码的参数与类中属性名一致。

List<User> getUser(User user);

sql代码

<!--    List<User> getUser(User user);-->
<select id="getUser" resultType="User">
    select * from t_user where username=#username
</select>

获取插入的数据自增的主键

useGeneratedKeys:设置使用自增的主键

keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参 数user对象的某个属性中

sql代码 

<!--    int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user
    values (null, #username, #password, null, null, null, null, null)
</insert>

 测试代码

@Test
public void test8() 
    User user = new User(null, "小白", "123456", null, null, null, null, null);
    System.out.println(user);
    mapper.insertUser(user);
    System.out.println(user);

输出结果

 

可以看到,第一次输出user的时候id的值为null,当插入数据之后,id变成了19。 

字段名和属性名建立映射

字段名和属性名建立一对一映射

当数据库字段名和用于获取的类的属性名不一致时,需要建立字段名和属性名的映射来获取数据。

属性名

字段名

mybatis代码

<!--
resultMap:设置自定义映射
属性:
id:表示自定义映射的唯一标识
type:查询的数据要映射的实体类的类型
子标签:
id:设置主键的映射关系
result:设置普通字段的映射关系
association:设置多对一的映射关系
collection:设置一对多的映射关系
属性:
property:设置映射关系中实体类中的属性名
column:设置映射关系中表中的字段名
-->


<resultMap id="userMap" type="TestUser">
    <id property="id" column="id"></id>
    <result property="name" column="userName"></result>
    <result property="email" column="userEmail"></result>
</resultMap>

<!--    List<TestUser> getAllUser();-->
<select id="getAllUser" resultMap="userMap">
    select *
    from t_user
</select>

字段名和属性名建立多对一映射

属性名

字段名

级联方式处理映射关系

mybatis代码

<resultMap id="userMap" type="TestUser">
    <id property="id" column="id"></id>
    <result property="name" column="userName"></result>
    <result property="email" column="userEmail"></result>
    <result property="user.password" column="password"></result>
    <result property="user.gender" column="gender"></result>
    <result property="user.admin" column="admin"></result>
</resultMap>

<!--    TestUser getUserById(@Param(value = "id") Integer id);-->
<select id="getUserById" resultMap="userMap">
    select *
    from t_user
    where id = #id
</select>

 association处理映射关系

<resultMap id="userMap" type="TestUser">
    <id property="id" column="id"></id>
    <result property="name" column="userName"></result>
    <result property="email" column="userEmail"></result>

    <association property="user" javaType="User">
        <id property="password" column="password"></id>
        <result property="gender" column="gender"></result>
        <result property="admin" column="admin"></result>
    </association>

</resultMap>

<!--    TestUser getUserById(@Param(value = "id") Integer id);-->
<select id="getUserById" resultMap="userMap">
    select *
    from t_user
    where id = #id
</select>

 分步查询处理映射关系

第二步的查询方式

<!--    User getUserByUserEmail(@Param(value = "email") String email);//通过邮箱查询用户信息-->
<select id="getUserByUserEmail" resultType="User">
    select *
    from t_user
    where userEmail = #userEmail
</select>

通过第一步查询结果中的Email为条件,分步查询结果赋值给user属性。 

<resultMap id="userMap" type="TestUser">
    <id property="id" column="id"></id>
    <result property="name" column="userName"></result>
    <result property="email" column="userEmail"></result>
    <association property="user" select="com.mybatis.mapper.UserMapper.getUserByUserEmail" column="userEmail"></association>
</resultMap>

<!--    TestUser getUserById(@Param(value = "id") Integer id);-->
<select id="getUserById" resultMap="userMap">
    select *
    from t_user
    where id = #id
</select>

字段名和属性名建立一对多映射

属性名

collection处理映射关系

  sql语句实际查询数据条数为3条

 将根据userEmail字段在t_activity表中的查询到的数据放入到了activeFormList当中

<resultMap id="getUserMap" type="TestUser">
    <id property="id" column="id"></id>
    <result property="name" column="userName"></result>
    <result property="email" column="userEmail"></result>
    <collection property="activeFormList" ofType="ActiveForm">
        <id property="activeId" column="activeId"></id>
        <result property="activeAdminEmail" column="activeAdminEmail"></result>
        <result property="activeAdminName" column="activeAdminName"></result>
    </collection>
</resultMap>

<!--    TestUser getUser(@Param(value = "id") Integer id);-->
<select id="getUser" resultMap="getUserMap">
    select *
    from t_user
             left join t_activity on t_user.userEmail = t_activity.activeAdminEmail
    where t_user.id = #id
</select>

 输出结果

  分步查询处理映射关系

首先在t_user表中查询id为7的用户,然后根据用户的邮箱在t_activity表中以userEmail为条件查询

第二步的查询代码

<!--    List<ActiveForm> getActivityByUserEmail(@Param(value = "userEmail")String userEmail);//根据用户邮箱获取用户发布的活动-->
<select id="getActivityByUserEmail" resultType="ActiveForm">
    select *
    from t_activity
    where activeAdminEmail = #activeAdminEmail
</select>

分步查询

<resultMap id="getUserMap" type="TestUser">
    <id property="id" column="id"></id>
    <result property="name" column="userName"></result>
    <result property="email" column="userEmail"></result>
    <collection property="activeFormList" fetchType="eager"
                select="com.mybatis.mapper.ActivityMapper.getActivityByUserEmail" column="userEmail">
    </collection>
</resultMap>


<!--    TestUser getUser(@Param(value = "id") Integer id);-->
<select id="getUser" resultMap="getUserMap">
    select * from t_user where id = #id
</select>

动态SQL

if标签

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中 的内容不会执行。

<!--    List<User> getUser(User user);-->
<select id="getUser" resultType="User">
    select * from t_user where username=#username

    <if test="userEmail != null and userEmail != '' ">
        and userEmail = #userEmail
    </if>

</select>

trim标签

trim用于去掉或添加标签中的内容,trim标签的功能比where标签的功能更强大,所以这里我只写另 trim标签,没有写where标签。

常用属性:

prefix:在trim标签中的内容的前面添加某些内容。

prefixOverrides:在trim标签中的内容的前面去掉某些内容。

suffix:在trim标签中的内容的后面添加某些内容。

suffixOverrides:在trim标签中的内容的后面去掉某些内容。

<!--    List<User> getUserByTrim(User user);-->
<select id="getUserByTrim" resultType="User">
    select * from t_user
    <trim prefix="where" prefixOverrides="and">
        <if test="userEmail != null and userEmail != ''">
            and userEmail = #userEmail
        </if>
        <if test="username != null and username != ''">
            and username = #username
        </if>
    </trim>
</select>

choose、when、otherwise标签

和if () ... else if () ... else意思一样。不满足<when>标签的内容时,判断下一个<when>标签,满足条件就结束,都不满足执行<otherwise>标签中的内容。

<!--    List<User> getUserByChoose(User user);-->
<select id="getUserByChoose" resultType="User">
    select * from t_user
    <trim prefix="where">
        <choose>
            <when test="userEmail != null and userEmail != ''">
                userEmail=#userEmail
            </when>
            <when test="username != null and username != ''">
                userEmail=#userEmail
            </when>
            <otherwise>
                id=#id
            </otherwise>
        </choose>
    </trim>
</select>

foreach标签

常用属性:

collection:设置要循环的数组或集合。

item:表示集合或数组中的每一个数据。

separator:设置循环体之间的分隔符。

open:设置foreach标签中的内容的开始符。

close:设置foreach标签中的内容的结束符。

查询id为ids中的数的数据

<!--    List<ActiveForm> getActivityByForeach(Integer[] ids);-->
<select id="getActivityByForeach" resultType="ActiveForm">
    select *
    from t_activity
    where activeId in
    <foreach collection="ids" item="item" open="(" separator="," close=")">
        #item
    </foreach>
</select>

批量删除数据

<!--    int deleteActivityByForeach(@Param(value = "ids")Integer[] ids);-->
<delete id="deleteActivityByForeach">
    delete from t_activity where activeId in
    <foreach collection="ids" item="item" open="(" separator="," close=")">
        #item
    </foreach>
</delete>

批量添加数据

<!--    int insertActivityByForeach(@Param(value = "activeFormList") List<ActiveForm> activeFormList);-->
<insert id="insertActivityByForeach">
    insert into t_activity values
    <foreach collection="activeFormList" item="active" separator=",">
        (null,#active.activeName,#active.acticeTime)
    </foreach>
</insert>

sql标签

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

<sql id="userSQL">id,username,password</sql>

<!--    User getUserByEmail(@Param(value = "email") String email);-->
<select id="getUserByEmail" resultType="User">
    select <include refid="userSQL"></include>
    from t_user
    where userEmail = #email;
</select>

 分页插件

 Maven链接

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

sql代码 

<!--    List<ActiveForm> getAllActive();-->
<select id="getAllActive" resultType="ActiveForm">
    select *
    from t_activity
</select>

 测试代码

@Test
public void test9() 
    PageHelper.startPage(1, 5);//设置分页,从第1条数据到第5条数据
    List<ActiveForm> allActive = mapper.getAllActive();
    for (ActiveForm activeForm : allActive)
        System.out.println(activeForm);

以上是关于MyBatis foreach 标签常用方法总结的主要内容,如果未能解决你的问题,请参考以下文章

[刘阳Java]_MyBatis_映射文件的常用标签总结_第5讲

mybatis foreach标签

MyBatis 常用的10 种通用的写法

mybatis <foreach>标签问题

Mybatis常用功能总结

Mybatis常用功能总结