AI 摘要

文章系统讲解 MyBatis 动态 SQL 核心标签:if、where、choose-when-otherwise、foreach、set、trim 及分页实现。通过多条件查询、集合参数、部分字段更新等场景,演示如何借助标签智能拼接 SQL,避免空值导致语法错误,提升代码健壮性与可维护性。

动态 SQL 处理简单的多参数查询

if 标签

通常情况下在实现多条件查询时,需要实现动态 SQL 的构建,如果没有实现动态 SQL 的构建,可能会出现 SQL 语句错误的问题。


示例:多条件查询可能存在的问题

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据条件(Params)查询用户列表
     *
     * @param realName 真实姓名
     * @param roleId 用户角色id
     * @return 用户列表
     */
    List<SysUser> getUserListByParams(@Param("realName") String realName, @Param("rId") Integer roleId);
  
    // ...

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{rId}
    </select>

    <!-- ... -->
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据条件(Params)查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByParams() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        // 只查询真实姓名包含“刘”的用户,不根据用户角色id查询
        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByParams("刘", null);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

    // ...

}

示例效果:


查询条件不符合要求的,不应该拼接到 SQL 语句中。

在 MyBatis 中,if 标签用于实现 SQL 语句的动态拼接功能,使用 if 标签可以有效地避免多条件查询拼接 SQL 语句时存在的隐患,保证了业务的正确性。


示例:if 标签

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        WHERE
        <if test="realName != null">
            realName LIKE CONCAT('%', #{realName}, '%')
        </if>
        <if test="rId != null">
            AND roleId = #{rId}
        </if>
    </select>
</mapper>

示例效果:


where 标签

MyBatis 提供了 where 标签,用于对 SQL 语句中的 where 关键字进行简化处理,直接替换 SQL 语句中的 where 关键字,可以智能地处理其内部多余的 and、or 等关键字,避免多余字符带来的语法错误。


示例:if + where 标签

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据条件(Params)查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByParams() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        // 只根据用户角色id查询,不根据真实姓名查询
        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByParams(null, 2);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

    // ...

}

示例效果:

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        <where>
            <if test="realName != null">
                AND realName LIKE CONCAT('%', #{realName}, '%')
            </if>
            <if test="rId != null">
                AND roleId = #{rId}
            </if>
        </where>
    </select>
</mapper>

示例效果:


choose + when + otherwise 标签

choose 是一个组合标签,通常与 when、otherwise 标签配合使用。

choose 标签的基本用法:

<choose>
    <when test="判断条件">
        ...
    </when>
    <when test="判断条件">
        ...
    </when>
    ...
    <otherwise>
        ...
    </otherwise>
</choose>

choose、when、otherwise 实现了类似于 Java 中 switch 语句的功能。


示例:choose + when + otherwise 标签

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据条件(Params)查询用户列表,三选一
     *
     * @param account 账号
     * @param realName 真实姓名
     * @param roleId 用户角色id
     * @return 用户列表
     */
    List<SysUser> getUserListByChoose(@Param("account") String account, @Param("realName") String realName, @Param("rId") Integer roleId);
  
}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByChoose" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        <where>
            <choose>
                <when test="account != null">
                    AND account = #{account}
                </when>
                <when test="realName != null">
                    AND realName LIKE CONCAT('%', #{realName}, '%')
                </when>
                <otherwise>
                    AND roleId = #{rId}
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据条件(Params)查询用户列表,三选一
     *
     * @return void
     */
    @Test
    public void getUserListByChoose() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByChoose("limingxing", null, 2);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName() + "  roleId => " + sysUser.getRoleId() + " account => "  + sysUser.getAccount());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


动态 SQL 处理集合参数

foreach 标签

foreach 标签用于在 SQL 语句中对集合进行迭代操作,更方便地构建出包含动态数量参数的 SQL 语句。

foreach 标签的基本用法:

<foreach collection="参数名称" item="元素别名"
    open="起始位置的拼接字符" close="结束位置的拼接字符" separator="元素之间的连接符">
    ...#{元素别名}
</foreach>

使用 foreach 标签处理数组类型参数

使用 foreach 标签可以遍历数组类型的参数,该参数在 foreach 标签中的参数名称默认为 array,可以使用 @Param 注解为参数取别名。


示例:使用 foreach 标签处理数组类型参数

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据用户角色id数组查询用户列表
     *
     * @param roleIds 用户角色id数组
     * @return 用户列表
     */
    List<SysUser> getUserListByRoleIdArray(@Param("roleIdsArray") Integer[] roleIds);
  
}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByRoleIdArray" resultType="cn.duozai.entity.SysUser">
        <!--
            拼接SQL语句:SELECT * FROM t_sys_user WHERE roleId in (1, 2, 3)
        -->
        SELECT * FROM t_sys_user WHERE roleId in
        <foreach collection="roleIdsArray" item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据用户角色id数组查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByRoleIdArray() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        Integer[] roleIds = {1,2};
        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByRoleIdArray(roleIds);

        for (SysUser sysUser : sysUserList) {
            System.out.println("SysUser => " + sysUser.getRealName() + "  roleId => " + sysUser.getRoleId());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


使用 foreach 标签处理 List 类型参数

使用 foreach 标签可以遍历 List 类型的参数,该参数在 foreach 标签中的参数名称默认为 list,可以使用 @Param 注解为参数取别名。


示例:使用 foreach 标签处理 List 类型参数

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据用户角色id列表查询用户列表
     *
     * @param roleIds 用户角色id列表
     * @return 用户列表
     */
    List<SysUser> getUserListByRoleIdList(List<Integer> roleIds);
  
}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByRoleIdList" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user WHERE roleId in
                <!-- 数组参数的默认名为array,List参数的默认名为list -->
        <foreach collection="list" item="item" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据用户角色id列表查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByRoleIdList() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<Integer> roleIds = new ArrayList<>();
        roleIds.add(1);
        roleIds.add(2);

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByRoleIdList(roleIds);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName() + "  roleId => " + sysUser.getRoleId());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


动态 SQL 处理更新功能

set 标签

通常情况下在实现修改功能时,需要实现动态 SQL 的构建,如果没有实现动态 SQL 的构建,可能会出现 SQL 语句错误的问题。


示例:修改操作可能存在的问题

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 修改用户信息
     *
     * @param sysUser 用户表实体类对象
     * @return SQL语句影响行数
     */
    int updateSysUser(SysUser sysUser);
  
}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <update id="updateSysUser" parameterType="cn.duozai.entity.SysUser">
        UPDATE t_sys_user SET realName = #{realName}, phone = #{phone}
        WHERE id = #{id}
    </update>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 修改用户信息
     *
     * @return void
     */
    @Test
    public void updateSysUser() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        // 将要修改的用户信息保存到实体类中
        // 根据用户id修改真实姓名
        SysUser sysUser = new SysUser();
        sysUser.setId(2);
        sysUser.setRealName("明星");

        int result = sqlSession.getMapper(SysUserMapper.class)
                .updateSysUser(sysUser);

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


只对部分属性的值进行修改时,没有做修改的属性不应该拼接到 SQL 语句中。

在 MyBatis 中,可以使用 set + if 标签实现 SQL 修改语句的动态拼接功能,set 标签直接替换 SQL 语句中的 set 关键字,可以智能地处理其内部多余的字符,避免多余字符带来的语法错误。


示例:if + set 标签

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <update id="updateSysUser" parameterType="cn.duozai.entity.SysUser">
        UPDATE t_sys_user
        <set>
            <if test="realName != null">
                realName = #{realName},
            </if>
            <if test="phone != null">
                phone = #{phone},
            </if>
        </set>
        WHERE id = #{id}
    </update>
</mapper>

示例效果:


动态 SQL 扩展

trim 标签

trim 标签可以实现类似 where、set 标签的功能,用于自动拼接或忽略 SQL 语句中的关键字。

trim 标签的基本用法:

<trim prefix="要拼接的前缀" suffix="要拼接的后缀"
    prefixOverrides="要忽略的前缀,用|隔开" suffixOverrides="要忽略的后缀,用|隔开">
    ...
</trim>

trim 标签高度灵活,通用性强,可以根据实际业务逻辑精确控制要添加或去除的内容。


示例:trim 标签

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 修改用户信息
     *
     * @param sysUser 用户表实体类对象
     * @return SQL语句影响行数
     */
    int updateSysUser(SysUser sysUser);
  
}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <update id="updateSysUser" parameterType="cn.duozai.entity.SysUser">
        UPDATE t_sys_user
        <trim prefix="set" suffixOverrides=",">
            <if test="realName != null">
                realName = #{realName},
            </if>
            <if test="phone != null">
                phone = #{phone},
            </if>
        </trim>
        WHERE id = #{id}
    </update>
</mapper>

分页实现

MyBatis 并没有对分页功能做过多的处理,而是使用数据库自带的分页功能。

MySQL 实现分页的方式:

  • 使用 LIMIT m, n 语法,其中 m \= (页码 - 1) * 页大小,n \= 页大小。
  • 使用 LIMIT a OFFSET b 语法,其中 a \= 页大小,b \= (页码 - 1) * 页大小。

示例:分页查询

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 分页查询用户列表
     *
     * @param m (页码-1)*页大小
     * @param n 页大小
     * @return 用户列表
     */
    List<SysUser> getUserListByPage(@Param("m") Integer m, @Param("n") Integer n);
  
}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <select id="getUserListByPage" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        LIMIT #{m}, #{n}
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 分页查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByPage() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        // 定义页码和页大小
        int pageIndex = 2;
        int pageSize = 5;

        // 计算 LIMIT m, n
        int m = (pageIndex-1)*pageSize;
        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByPage(m, pageSize);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果: