MyBatis——动态SQL

时间:2024-04-08 18:26:02

MyBatis动态SQL 

MyBatis 的一个强大的特性之一就是它的动态 SQL 能力。也就是可以根据不同的条件拼接SQL语句。

以下是动态SQL在XML中支持的几种元素

if

if元素通常用于where语句中,通过判断参数值来决定是否使用某个查询条件

假设现在有一个需求:实现一个用户管理高级查询功能,根据用户输入的条件去检索用户信息

创建实体类User和映射器接口
package org.example.entity;

import lombok.Data;

@Data
public class User {
    private Integer userId;
    private String userName;
    private String password;
}
package org.example.mapper;

import com.github.pagehelper.Page;
import org.apache.ibatis.annotations.Param;
import org.example.entity.User;

import java.util.List;
import java.util.Map;
import java.util.Objects;

public interface UserMapper {
    User findUser(Map<String,String> data);
}

不使用动态SQL时,实现上述需求的映射文件如下:

<mapper namespace="com.mybatis.mapper.UserMapper">
    <select id="findUserByPhoneOrEmail"    
            resultType="com.mybatis.mapper.User">
        select * 
        from t_user 
        where user_name = #{userName} and password = #{password}
    </select>
</mapper>

调用该方法时,传入的Map类型的实际参数中,必须存在userName和password这两个key值 

不使用动态SQL时,只有同时输入phone和email两个条件时,才能查出正确结果,此时需要if元素来解决这个问题

<select id="findUserByPhoneOrEmail" resultType="User">
     select * 
  from user where 1 = 1
  <if test="userName != null and !''.equals(userName)">
        and user_name = #{userName}
  </if>
  <if test="password != null and !''.equals(password)">
        and password = #{password}
  </if>
</select>

 如果不想让where条件中出现“1=1”这种表达式时,就需要使用where元素 where元素的作用:如果该元素中有内容,就在生成SQL语句时加上where条件,如果该元素的内容以AND或者OR开头,就去除这两个单词

    <resultMap id="userMap" type="User">
        <result property="userId" column="user_id"/>
        <result property="userName" column="user_name"/>
        <result property="password" column="password"/>
    </resultMap>
    <select id="findUser" resultMap="userMap">
        
        动态sql
        select * from t_user 
        <where>
            <if test="userName != null and !''.equals(userName)">
                and user_name = #{userName}
            </if>
            <if test="password != null and !''.equals(password)">
                and password = #{password}
            </if>
        </where>
                                 
    </select>

使用if元素实现动态列更新,即更新用户信息时,只更新有变化的字段

   <update id="updateUser">
        update t_user set
        <if test="userName != null and !''.equals(userName)">
            user_name = #{userName},
        </if>
        <if test="password != null and !''.equals(password)">
            password = #{password},
        </if>
        user_id = #{userId}
        where user_id = #{userId}
    </update>

 实现动态列更新时,可以使用set元素 set元素作用:当该元素有内容时,生成SQL语句时就加上set语句;当set元素的内容以逗号结尾时,去掉逗号

    <update id="updateUser">
        update t_user
        <set>
            <if test="userName != null and !''.equals(userName)">
                user_name = #{userName},
            </if>
            <if test="password != null and !''.equals(password)">
                password = #{password},
            </if>
        </set>
        where user_id = #{userId}
    </update>    

 

choose (when, otherwise)

choose元素中包含when和otherwise两个子元素 一个choose元素中至少有一个when子元素 一个choose元素中可以包含0个或1个otherwise子元素 使用choose元素可以实现类似if…else…的逻辑

    <select id="findUserByUserIdOrUserName" resultMap="userMap">
        select * from t_user where 1=1
        <choose>
            <when test="userId != null and !''.equals(userId)">
                and user_id = #{userId}
            </when>
            <when test="userName != null and !''.equals(userName)">
                and user_name = #{userName}
            </when>
        </choose>
    </select>
trim (where, set)

trim元素可以实现where和set元素的功能 trim元素对应的where功能的实现 

trim元素的属性,都在trim元素包含内容时起作用:

prefix:给内容增加该属性指定的前缀

prefixOverrides:把内容中匹配的前缀字符串去掉

suffix:给内容增加该属性指定的后缀

suffixOverrides:把内容中匹配的后缀字符串去掉 

    <select id="findUser" resultMap="userMap">
        select * from t_user
        <trim prefix="where" prefixOverrides="and">
            <if test="userName != null and !''.equals(userName)">
                and user_name = #{userName}
            </if>
            <if test="password != null and !''.equals(password)">
                and password = #{password}
            </if>
        </trim>

    </select>
    <update id="updateUser">
        update t_user
        <trim prefix="set" suffixOverrides=",">
            <if test="userName != null and !''.equals(userName)">
                user_name = #{userName},
            </if>
            <if test="password != null and !''.equals(password)">
                password = #{password},
            </if>
        </trim>
        where user_id = #{userId}
    </update>
foreach

foreach可以对数组、Map或实现了Iterable接口(如:List、Set)的对象进行遍历 映射接口中添加如下方法

package org.example.mapper;

import com.github.pagehelper.Page;
import org.apache.ibatis.annotations.Param;
import org.example.entity.User;

import java.util.List;
import java.util.Map;
import java.util.Objects;

public interface UserMapper {
    List<User> findUserByDy(@Param("map") Map<String, Object> data);
}
    <select id="findUserByDy" resultMap="userMap">
        select * from t_user where 1=1 and
        <foreach collection="map" item="value" index="key" separator="and">
            ${key} = #{value}
        </foreach>
    </select>

 foreach各个属性的含义:

collection属性的取值有多种情况

当参数为List集合时,collection取值为list

当参数为数组时,collection取值为array

当参数为Map类型时,collection取值默认情况下为_parameter,也可以使用@Param注解指定名字,collection取值为该注解指定的名字 

foreach还能实现批量插入。目前支持批量插入的数据库有DB2、SQL Server 2008及以上版本、MySQL等 在映射接口中添加批量插入的方法

package org.example.mapper;

import com.github.pagehelper.Page;
import org.apache.ibatis.annotations.Param;
import org.example.entity.User;

import java.util.List;
import java.util.Map;
import java.util.Objects;

public interface UserMapper {
    int insertUserBatch(List<User> userList);
}

MySQL数据库还支持批量插入后,返回所有记录的主键的操作,MyBatis要想实现此功能,需要修改XML映射文件 

    <insert id="insertUserBatch" useGeneratedKeys="true" keyProperty="userId">
        insert into t_user(user_name,password) values
        <foreach collection="list" item="user" separator=",">
            (#{user.userName},#{user.password})
        </foreach>
    </insert>

 foreach实现动态更新,当参数是Map类型时,foreach元素的index属性值对应的不是索引值,而是Map中的key 实现通过指定的列名和对应的值去更新数据,在映射接口中添加如下方法

public int updateByMap(Map<String, Object> map);

这里key作为列名,对应的值作为该列的值,通过foreach将需要更新的字段拼接在SQL语句中 

<update id="updateByMap">
    update user set
    <foreach collection="_parameter" item="value" 
        index="key" separator=",">
        ${key} = #{value}
    </foreach>
    where id = #{id}
</update>

上述方法在调用时,Map类型的实参中的key值要与表中字段名对应 也可以将映射器接口中方法的参数添加@Param注解

public int updateByMap(@Param("map")Map<String, 
    Object> map);

 映射文件修改如下

<update id="updateByMap">
    update user set
    <foreach collection="map" item="value" 
        index="key" separator=",">
        ${key} = #{value}
    </foreach>
    where id = #{map.id}
</update>

 

bind

bind可以使用OGNL表达式创建一个变量并将其绑定到上下文中。如:在模糊查询中可以使用

public List<User> findUserLike(
                     @Param("name")String name);

bind可以使用OGNL表达式创建一个变量并将其绑定到上下文中。如:在模糊查询中可以使用

<select id="findUserLike" resultMap="userMap">
   <bind name="userName" value="'%' + name + '%'"/>
   select * from user 
       where user_name like #{userName}
</select>