HomeBlogAboutLogin
返回列表

MyBatis手册

MyBatis XML 指南

1. MyBatis XML 是干什么的?

MyBatis XML 文件主要用来写 SQL。

比如 Java 里有一个方法:

SysUser selectUserById(Long userId);

XML 里就有一段 SQL:

<select id="selectUserById" resultType="SysUser">
    SELECT user_id, user_name, nick_name
    FROM sys_user
    WHERE user_id = #{userId}
</select>

它们通过:

id="selectUserById"

和 Java 方法名对应。


2. Mapper 接口和 XML 的对应关系

Mapper 接口

public interface SysUserMapper {

    SysUser selectUserById(Long userId);

    List<SysUser> selectUserList(SysUser user);

    int insertUser(SysUser user);

    int updateUser(SysUser user);

    int deleteUserById(Long userId);
}

XML

<mapper namespace="com.ruoyi.system.mapper.SysUserMapper">

    <select id="selectUserById" resultType="SysUser">
        SELECT *
        FROM sys_user
        WHERE user_id = #{userId}
    </select>

</mapper>

注意两个对应关系:

namespace = Mapper 接口完整路径
id = Mapper 接口方法名

也就是:

<mapper namespace="com.ruoyi.system.mapper.SysUserMapper">

对应:

package com.ruoyi.system.mapper;

public interface SysUserMapper {
}

然后:

<select id="selectUserById">

对应:

SysUser selectUserById(Long userId);

3. MyBatis XML 基本结构

一个典型 Mapper 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="com.ruoyi.system.mapper.SysUserMapper">

    <select id="selectUserById" resultType="SysUser">
        SELECT *
        FROM sys_user
        WHERE user_id = #{userId}
    </select>

</mapper>

说明:

<?xml ... ?>:XML 声明
<!DOCTYPE mapper ...>:MyBatis XML 约束
<mapper>:整个 Mapper XML 的根标签
namespace:绑定 Java Mapper 接口
<select>:查询语句

4. select 标签

<select> 用来写查询 SQL。

查询单个对象

<select id="selectUserById" resultType="SysUser">
    SELECT user_id, user_name, nick_name
    FROM sys_user
    WHERE user_id = #{userId}
</select>

Mapper 接口:

SysUser selectUserById(Long userId);

意思是:

根据 userId 查询一条用户数据,返回 SysUser 对象

查询列表

XML 里还是写每一行的类型:

<select id="selectUserList" resultType="SysUser">
    SELECT user_id, user_name, nick_name
    FROM sys_user
</select>

Mapper 接口:

List<SysUser> selectUserList();

注意:

返回 List<SysUser> 时,XML 的 resultType 仍然写 SysUser

因为 resultType 表示每一行数据封装成什么对象。


查询单个字段

<select id="selectUserNameById" resultType="String">
    SELECT user_name
    FROM sys_user
    WHERE user_id = #{userId}
</select>

Mapper 接口:

String selectUserNameById(Long userId);

查询数量

<select id="countUser" resultType="int">
    SELECT COUNT(1)
    FROM sys_user
</select>

Mapper 接口:

int countUser();

5. insert 标签

<insert> 用来新增数据。

简单新增

<insert id="insertUser" parameterType="SysUser">
    INSERT INTO sys_user(
        user_name,
        nick_name,
        email
    )
    VALUES(
        #{userName},
        #{nickName},
        #{email}
    )
</insert>

Mapper 接口:

int insertUser(SysUser user);

返回 int 表示影响行数。

1:新增成功,影响 1 行
0:没有影响行

动态新增

字段很多时,常用动态 insert:

<insert id="insertUser" parameterType="SysUser">
    INSERT INTO sys_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="userName != null and userName != ''">user_name,</if>
        <if test="nickName != null and nickName != ''">nick_name,</if>
        <if test="email != null and email != ''">email,</if>
        <if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
        <if test="deptId != null">dept_id,</if>
        <if test="status != null and status != ''">status,</if>
        <if test="createBy != null and createBy != ''">create_by,</if>
        <if test="createTime != null">create_time,</if>
    </trim>
    <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
        <if test="userName != null and userName != ''">#{userName},</if>
        <if test="nickName != null and nickName != ''">#{nickName},</if>
        <if test="email != null and email != ''">#{email},</if>
        <if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
        <if test="deptId != null">#{deptId},</if>
        <if test="status != null and status != ''">#{status},</if>
        <if test="createBy != null and createBy != ''">#{createBy},</if>
        <if test="createTime != null">#{createTime},</if>
    </trim>
</insert>

它的意思是:

哪个 Java 属性有值,就插入哪个数据库字段

6. update 标签

<update> 用来修改数据。

简单修改

<update id="updateUser" parameterType="SysUser">
    UPDATE sys_user
    SET user_name = #{userName}
    WHERE user_id = #{userId}
</update>

Mapper 接口:

int updateUser(SysUser user);

多字段动态修改

<update id="updateUser" parameterType="SysUser">
    UPDATE sys_user
    <set>
        <if test="userName != null and userName != ''">
            user_name = #{userName},
        </if>
        <if test="nickName != null and nickName != ''">
            nick_name = #{nickName},
        </if>
        <if test="email != null and email != ''">
            email = #{email},
        </if>
        <if test="phonenumber != null and phonenumber != ''">
            phonenumber = #{phonenumber},
        </if>
        <if test="deptId != null">
            dept_id = #{deptId},
        </if>
        <if test="status != null and status != ''">
            status = #{status},
        </if>
        <if test="remark != null">
            remark = #{remark},
        </if>
        <if test="updateBy != null and updateBy != ''">
            update_by = #{updateBy},
        </if>
        <if test="updateTime != null">
            update_time = #{updateTime},
        </if>
    </set>
    WHERE user_id = #{userId}
</update>

<set> 的作用:

自动加 SET
自动去掉最后多余的逗号

比如只传了 userNameemail,最终 SQL 类似:

UPDATE sys_user
SET user_name = ?,
    email = ?
WHERE user_id = ?

7. delete 标签

<delete> 用来删除数据。

根据 ID 删除

<delete id="deleteUserById">
    DELETE FROM sys_user
    WHERE user_id = #{userId}
</delete>

Mapper 接口:

int deleteUserById(Long userId);

批量删除

<delete id="deleteUserByIds">
    DELETE FROM sys_user
    WHERE user_id IN
    <foreach collection="array" item="userId" open="(" separator="," close=")">
        #{userId}
    </foreach>
</delete>

Mapper 接口:

int deleteUserByIds(Long[] userIds);

如果传入:

Long[] userIds = {1L, 2L, 3L};

最终 SQL 类似:

DELETE FROM sys_user
WHERE user_id IN (1, 2, 3)

8. parameterType 是什么?

parameterType 表示入参类型。

例如:

<select id="selectUserById" parameterType="Long" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
</select>

意思是:

这个 SQL 需要传入一个 Long 类型参数

但是很多时候 parameterType 可以省略。

比如:

<select id="selectUserById" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
</select>

对应接口:

SysUser selectUserById(Long userId);

MyBatis 可以从 Mapper 方法里推断入参类型。


9. resultType 是什么?

resultType 表示查询结果返回类型。

<select id="selectUserById" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
</select>

意思是:

把查询结果封装成 SysUser 对象

如果查询列表:

List<SysUser> selectUserList();

XML 仍然写:

resultType="SysUser"

因为它表示每一行封装成 SysUser


10. parameterType 和 resultType 区别

属性 作用 常用标签
parameterType 入参类型 select / insert / update / delete
resultType 查询返回类型 select

记法:

parameterType:传进来的是什么
resultType:查出去的是什么

例子:

<select id="selectUserById"
        parameterType="Long"
        resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
</select>

含义:

传入 Long 类型 userId
返回 SysUser 对象

11. resultType 可以省略吗?

select 查询一般不能省略 resultTyperesultMap

错误示例:

<select id="selectUserById">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
</select>

可能会报错:

A query was run and no Result Maps were found for the Mapped Statement

所以查询语句一般要写:

resultType="SysUser"

或者:

resultMap="SysUserResult"

12. resultMap 是什么?

resultMap 是手动映射数据库字段和 Java 属性。

当字段名和属性名不能自动对应,或者关系复杂时,用 resultMap

<resultMap id="SysUserResult" type="SysUser">
    <id property="userId" column="user_id"/>
    <result property="userName" column="user_name"/>
    <result property="nickName" column="nick_name"/>
    <result property="email" column="email"/>
</resultMap>

然后查询时使用:

<select id="selectUserById" resultMap="SysUserResult">
    SELECT user_id, user_name, nick_name, email
    FROM sys_user
    WHERE user_id = #{userId}
</select>

说明:

property="userId"

是 Java 属性名。

column="user_id"

是数据库字段名。


13. resultType 和 resultMap 区别

对比项 resultType resultMap
作用 自动映射 手动映射
适合 简单查询 复杂查询
字段对应 依赖自动规则 自己明确指定
一对多/一对一 不适合 适合

简单查询:

<select id="selectUserById" resultType="SysUser">
    SELECT user_id, user_name
    FROM sys_user
</select>

复杂查询:

<select id="selectUserById" resultMap="SysUserResult">
    SELECT user_id, user_name
    FROM sys_user
</select>

14. #{} 是什么?

#{} 用来取 Java 参数,并作为 SQL 参数传入。

WHERE user_id = #{userId}

如果调用:

selectUserById(10086L);

最终相当于:

WHERE user_id = 10086

它使用的是预编译参数,比较安全,可以防 SQL 注入。

推荐大多数场景都用 #{}


15. ${} 是什么?

${} 是字符串直接拼接。

例如:

ORDER BY ${orderBy}

如果传入:

orderBy = "create_time DESC"

最终 SQL 是:

ORDER BY create_time DESC

注意:${} 有 SQL 注入风险。

比如用户传入:

id; DROP TABLE sys_user;

就可能造成危险。

所以:

字段值用 #{}
表名、字段名、排序字段等无法用 #{} 的场景,才谨慎用 ${}

16. #{}${} 区别

写法 含义 是否安全 适合
#{} 参数占位 安全 字段值
${} 字符串拼接 不安全 表名、字段名、排序字段

推荐:

WHERE user_name = #{userName}

不推荐:

WHERE user_name = '${userName}'

17. <if> 标签

<if> 是条件判断。

<if test="userName != null and userName != ''">
    AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>

意思是:

如果 userName 有值,才拼接这个条件

如果 userName 没值,这段 SQL 不会出现。


18. if 的 test 属性

test 里面写判断条件。

<if test="status != null and status != ''">
    AND status = #{status}
</if>

判断的是 Java 参数对象里的属性。

如果 Mapper 方法是:

List<SysUser> selectUserList(SysUser user);

实体类有:

private String status;

那么:

status != null

判断的就是:

user.getStatus() != null

19. 不同类型的 if 判断

字符串

<if test="userName != null and userName != ''">
    AND user_name = #{userName}
</if>

数字

<if test="deptId != null">
    AND dept_id = #{deptId}
</if>

数字不要判断 != ''

时间

<if test="beginTime != null">
    AND create_time &gt;= #{beginTime}
</if>

金额

<if test="amount != null">
    AND amount = #{amount}
</if>

集合

<if test="ids != null and ids.size() > 0">
    ...
</if>

20. XML 里的特殊符号

XML 中不能直接写 <,否则会被当成标签。

所以要转义。

SQL 符号 XML 写法
< &lt;
<= &lt;=
> &gt;
>= &gt;=
& &amp;

比如:

<if test="beginTime != null">
    AND create_time &gt;= #{beginTime}
</if>
<if test="endTime != null">
    AND create_time &lt;= #{endTime}
</if>

也可以用 CDATA:

AND create_time <![CDATA[ >= ]]> #{beginTime}

21. <where> 标签

<where> 用来自动处理 WHERE 和多余的 AND

普通写法容易出错:

SELECT *
FROM sys_user
WHERE
<if test="userName != null">
    AND user_name = #{userName}
</if>

可能生成:

WHERE AND user_name = ?

错误。

<where>

<select id="selectUserList" resultType="SysUser">
    SELECT *
    FROM sys_user
    <where>
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
        </if>
        <if test="deptId != null">
            AND dept_id = #{deptId}
        </if>
    </where>
</select>

<where> 的作用:

有条件时自动加 WHERE
自动去掉最前面的 AND / OR
没有条件时不加 WHERE

22. <set> 标签

<set> 用于动态 update。

<update id="updateUser" parameterType="SysUser">
    UPDATE sys_user
    <set>
        <if test="userName != null and userName != ''">
            user_name = #{userName},
        </if>
        <if test="email != null and email != ''">
            email = #{email},
        </if>
    </set>
    WHERE user_id = #{userId}
</update>

作用:

自动加 SET
自动去掉最后一个逗号

23. <trim> 标签

<trim> 用来定制前缀、后缀、去掉前后多余内容。

常见在 insert 里使用:

<trim prefix="(" suffix=")" suffixOverrides=",">
    <if test="userName != null and userName != ''">user_name,</if>
    <if test="nickName != null and nickName != ''">nick_name,</if>
</trim>

属性说明:

属性 含义
prefix 前面加什么
suffix 后面加什么
prefixOverrides 去掉前面多余的内容
suffixOverrides 去掉后面多余的内容

trim 示例一:insert 字段

<trim prefix="(" suffix=")" suffixOverrides=",">
    user_name,
    nick_name,
</trim>

最终:

(user_name, nick_name)

trim 示例二:模拟 where

<trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="userName != null">
        AND user_name = #{userName}
    </if>
</trim>

<where> 类似。


trim 示例三:模拟 set

<trim prefix="SET" suffixOverrides=",">
    <if test="userName != null">
        user_name = #{userName},
    </if>
</trim>

<set> 类似。


24. <foreach> 标签

<foreach> 用来循环集合。

最常见是 IN 查询,例如前端批量勾选。

<select id="selectUserByIds" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id IN
    <foreach collection="array" item="userId" open="(" separator="," close=")">
        #{userId}
    </foreach>
</select>

Mapper 接口:

List<SysUser> selectUserByIds(Long[] userIds);

如果传入:

Long[] userIds = {1L, 2L, 3L};

java中[]表示数组类型,{}表示数组初始化内容

最终 SQL:

SELECT *
FROM sys_user
WHERE user_id IN (1, 2, 3)

如果 Mapper 用 @Param("ids")

List<SysUser> selectUserByIds(@Param("ids") List<Long> ids);

collection才可以用"ids",否则用默认的内置变量 "arrary"

<select id="selectUserByIds" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

foreach 属性说明

属性 含义
collection 要循环的集合
item 每一项的变量名
index 下标
open 开始符号
separator 分隔符
close 结束符号

collection 常见取值

参数是数组

List<SysUser> selectUserByIds(Long[] userIds);

XML:

<foreach collection="array" item="userId" open="(" separator="," close=")">
    #{userId}
</foreach>

参数是 List

List<SysUser> selectUserByIds(List<Long> userIds);

XML:

<foreach collection="list" item="userId" open="(" separator="," close=")">
    #{userId}
</foreach>

使用 @Param

List<SysUser> selectUserByIds(@Param("ids") List<Long> ids);

XML:

<foreach collection="ids" item="id" open="(" separator="," close=")">
    #{id}
</foreach>

推荐使用 @Param,更清晰。


25. <choose><when><otherwise>

类似 Java 里的 if / else if / else

<select id="selectUserList" resultType="SysUser">
    SELECT *
    FROM sys_user
    <where>
        <choose>
            <when test="userName != null and userName != ''">
                AND user_name = #{userName}
            </when>
            <when test="phonenumber != null and phonenumber != ''">
                AND phonenumber = #{phonenumber}
            </when>
            <otherwise>
                AND status = '0'
            </otherwise>
        </choose>
    </where>
</select>

意思:

如果 userName 有值,就按 userName 查
否则如果 phonenumber 有值,就按 phonenumber 查
否则默认查 status = '0'

26. <sql><include>

<sql> 用来定义可复用 SQL 片段。

<include> 用来引用 SQL 片段。

定义字段片段

<sql id="selectUserColumns">
    user_id,
    dept_id,
    user_name,
    nick_name,
    email,
    phonenumber,
    status,
    create_time,
    remark
</sql>

引用

<select id="selectUserById" resultType="SysUser">
    SELECT
    <include refid="selectUserColumns"/>
    FROM sys_user
    WHERE user_id = #{userId}
</select>

这样不用每个查询都重复写字段。


若依常见写法

<sql id="selectSysUserVo">
    SELECT
        u.user_id,
        u.dept_id,
        u.user_name,
        u.nick_name,
        u.email,
        u.phonenumber,
        u.status,
        u.create_time
    FROM sys_user u
</sql>

<select id="selectUserById" resultType="SysUser">
    <include refid="selectSysUserVo"/>
    WHERE u.user_id = #{userId}
</select>

27. 表别名

复杂 SQL 常用表别名。

<select id="selectUserList" resultType="SysUser">
    SELECT
        u.user_id,
        u.user_name,
        d.dept_name
    FROM sys_user u
    LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
    WHERE u.del_flag = '0'
</select>

这里:

sys_user u:u 是 sys_user 的别名
sys_dept d:d 是 sys_dept 的别名

后面可以写:

u.user_id
d.dept_name

28. 多表查询

例如查询用户和部门:

<select id="selectUserDeptList" resultType="SysUserVo">
    SELECT
        u.user_id,
        u.user_name,
        u.nick_name,
        d.dept_id,
        d.dept_name
    FROM sys_user u
    LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
    WHERE u.del_flag = '0'
</select>

如果 SysUser 里没有 deptName,可以新建一个 VO:

public class SysUserVo {
    private Long userId;
    private String userName;
    private String nickName;
    private Long deptId;
    private String deptName;
}

XML:

resultType="SysUserVo"

29. 模糊查询 LIKE

MySQL 常见写法:

<if test="userName != null and userName != ''">
    AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>

PostgreSQL / 瀚高也可以:

<if test="userName != null and userName != ''">
    AND user_name LIKE '%' || #{userName} || '%'
</if>

若依里经常用 MySQL 风格:

like concat('%', #{userName}, '%')

具体看数据库支持。


30. 时间范围查询

<if test="beginTime != null">
    AND create_time &gt;= #{beginTime}
</if>
<if test="endTime != null">
    AND create_time &lt;= #{endTime}
</if>

如果参数放在 params 里,若依常见:

<if test="params.beginTime != null and params.beginTime != ''">
    AND create_time &gt;= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
    AND create_time &lt;= #{params.endTime}
</if>

31. 若依里的 params 是什么?

若依很多实体继承了 BaseEntity,里面有:

private Map<String, Object> params;

所以 XML 里经常看到:

<if test="params.beginTime != null and params.beginTime != ''">
    AND create_time &gt;= #{params.beginTime}
</if>

意思是取:

entity.getParams().get("beginTime")

32. order by 排序

固定排序:

ORDER BY create_time DESC

动态排序时可能会用 ${}

ORDER BY ${orderByColumn} ${isAsc}

注意:${} 有 SQL 注入风险。

所以动态排序必须在后端校验字段白名单,不能让前端随便传。

例如只允许:

create_time
user_name
status

33. 分页查询

MyBatis XML 本身不一定直接写分页。

若依常见是 PageHelper 自动分页:

Controller 或 Service 调用:

startPage();
List<SysUser> list = userMapper.selectUserList(user);

XML 只写普通查询:

<select id="selectUserList" resultType="SysUser">
    SELECT *
    FROM sys_user
    <where>
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
    </where>
</select>

PageHelper 会自动拼分页 SQL。


34. 批量新增

<insert id="batchInsertUser">
    INSERT INTO sys_user(
        user_name,
        nick_name,
        email
    )
    VALUES
    <foreach collection="list" item="user" separator=",">
        (
            #{user.userName},
            #{user.nickName},
            #{user.email}
        )
    </foreach>
</insert>

Mapper:

int batchInsertUser(List<SysUser> users);

建议加 @Param

int batchInsertUser(@Param("list") List<SysUser> users);

35. 批量修改

批量修改有两种常见方式。

方式一:循环多条 update

<update id="batchUpdateUser">
    <foreach collection="list" item="user" separator=";">
        UPDATE sys_user
        SET user_name = #{user.userName},
            nick_name = #{user.nickName}
        WHERE user_id = #{user.userId}
    </foreach>
</update>

有些数据库或连接配置不允许多语句执行,要谨慎。


方式二:CASE WHEN

<update id="batchUpdateUserStatus">
    UPDATE sys_user
    SET status = CASE user_id
        <foreach collection="list" item="user">
            WHEN #{user.userId} THEN #{user.status}
        </foreach>
    END
    WHERE user_id IN
    <foreach collection="list" item="user" open="(" separator="," close=")">
        #{user.userId}
    </foreach>
</update>

适合批量更新同一个字段。


36. 逻辑删除

很多业务不是物理删除,而是逻辑删除。

物理删除:

<delete id="deleteUserById">
    DELETE FROM sys_user
    WHERE user_id = #{userId}
</delete>

逻辑删除:

<update id="deleteUserById">
    UPDATE sys_user
    SET del_flag = '2'
    WHERE user_id = #{userId}
</update>

若依常见:

del_flag = '0':正常
del_flag = '2':删除

查询时要加:

AND del_flag = '0'

37. 使用别名 resultType

有时候 XML 写:

resultType="SysUser"

不是完整路径:

resultType="com.ruoyi.system.domain.SysUser"

这是因为配置了别名包。

若依常见配置:

mybatis:
  typeAliasesPackage: com.ruoyi.**.domain

所以可以直接写:

resultType="SysUser"
parameterType="SysUser"

否则就要写完整类名:

resultType="com.ruoyi.system.domain.SysUser"

38. 数据库字段和 Java 属性自动映射

数据库字段:

user_id
user_name
create_time

Java 属性:

userId
userName
createTime

如果开启驼峰映射:

mybatis:
  configuration:
    map-underscore-to-camel-case: true

那么会自动映射:

user_id     -> userId
user_name   -> userName
create_time -> createTime

如果没开启,就需要起别名:

SELECT
    user_id AS userId,
    user_name AS userName,
    create_time AS createTime
FROM sys_user

或者用 resultMap


39. select 字段需要全部写吗?

不需要。

如果 SysUser 有 20 个字段,但 SQL 只查 3 个:

<select id="selectUserById" resultType="SysUser">
    SELECT user_id, user_name, nick_name
    FROM sys_user
    WHERE user_id = #{userId}
</select>

那么 MyBatis 只会给这 3 个属性赋值。

其他没查的字段是:

null

或者基础类型默认值。

建议:

列表页:只查需要展示的字段
详情页:查完整字段
公共查询:用 <sql> + <include> 复用字段

40. SELECT * 可以吗?

可以。

<select id="selectUserById" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
</select>

但不推荐长期大量使用。

原因:

查了不需要的字段
表字段变多后接口无意中变重
不利于维护
可能查出敏感字段,比如 password

内部管理系统字段不多时可以用,但正式代码建议明确字段。


41. insert 动态字段需要注意什么?

动态 insert 时,字段名和值必须一一对应。

正确:

<if test="userName != null">user_name,</if>

对应:

<if test="userName != null">#{userName},</if>

错误:

<if test="userName != null">user_name,</if>

但值里面忘了:

#{userName}

会导致字段和值数量不一致,SQL 报错。


42. update 动态字段需要注意什么?

一定要有 WHERE

危险写法:

<update id="updateUser">
    UPDATE sys_user
    SET status = '1'
</update>

这会更新整张表。

正确:

<update id="updateUser">
    UPDATE sys_user
    SET status = '1'
    WHERE user_id = #{userId}
</update>

写 update 时重点检查:

有没有 WHERE
WHERE 条件是不是主键或准确条件
动态 set 是否可能为空

43. 动态 update 的 set 为空问题

如果所有字段都没有值:

<update id="updateUser" parameterType="SysUser">
    UPDATE sys_user
    <set>
        <if test="userName != null">user_name = #{userName},</if>
        <if test="email != null">email = #{email},</if>
    </set>
    WHERE user_id = #{userId}
</update>

可能生成非法 SQL:

UPDATE sys_user
WHERE user_id = ?

所以 Service 层最好校验:

至少有一个字段需要更新

44. 参数是对象时怎么取值?

Mapper:

int insertUser(SysUser user);

XML:

#{userName}
#{nickName}
#{email}

表示取:

user.getUserName()
user.getNickName()
user.getEmail()

45. 参数是多个普通值时怎么取值?

推荐用 @Param

Mapper:

SysUser selectUser(
    @Param("userId") Long userId,
    @Param("status") String status
);

XML:

<select id="selectUser" resultType="SysUser">
    SELECT *
    FROM sys_user
    WHERE user_id = #{userId}
      AND status = #{status}
</select>

46. 参数是对象 + 普通值时怎么取?

Mapper:

List<SysUser> selectUserList(
    @Param("user") SysUser user,
    @Param("status") String status
);

XML:

<select id="selectUserList" resultType="SysUser">
    SELECT *
    FROM sys_user
    <where>
        <if test="user.userName != null and user.userName != ''">
            AND user_name LIKE CONCAT('%', #{user.userName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
        </if>
    </where>
</select>

注意对象属性要写:

user.userName

47. 常见 XML 错误

错误一:忘写 resultType

<select id="selectUserById">
    SELECT *
    FROM sys_user
</select>

查询必须写 resultTyperesultMap


错误二:字段名和属性名混淆

错误:

<if test="user_name != null">
    user_name,
</if>

如果 Java 实体属性是 userName,应该写:

<if test="userName != null">
    user_name,
</if>

test 里写 Java 属性名,不是数据库字段名。


错误三:数字判断空字符串

不推荐:

<if test="deptId != null and deptId != ''">

推荐:

<if test="deptId != null">

错误四:XML 里直接写 <

错误:

AND create_time <= #{endTime}

应写:

AND create_time &lt;= #{endTime}

或者:

AND create_time <![CDATA[ <= ]]> #{endTime}

错误五:动态 SQL 多逗号

如果不用 <trim><set>,容易出现:

INSERT INTO sys_user(user_name, nick_name,)

或者:

UPDATE sys_user SET user_name = ?, email = ?, WHERE id = ?

<trim><set> 解决。


48. 常见完整示例:查询列表

<select id="selectUserList" parameterType="SysUser" resultType="SysUser">
    SELECT
        user_id,
        dept_id,
        user_name,
        nick_name,
        email,
        phonenumber,
        status,
        create_time
    FROM sys_user
    <where>
        del_flag = '0'
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
        <if test="phonenumber != null and phonenumber != ''">
            AND phonenumber LIKE CONCAT('%', #{phonenumber}, '%')
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
        </if>
        <if test="deptId != null">
            AND dept_id = #{deptId}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

49. 常见完整示例:详情查询

<select id="selectUserById" resultType="SysUser">
    SELECT
        user_id,
        dept_id,
        user_name,
        nick_name,
        email,
        phonenumber,
        sex,
        avatar,
        status,
        create_time,
        remark
    FROM sys_user
    WHERE user_id = #{userId}
      AND del_flag = '0'
</select>

50. 常见完整示例:新增

<insert id="insertUser" parameterType="SysUser">
    INSERT INTO sys_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="deptId != null">dept_id,</if>
        <if test="userName != null and userName != ''">user_name,</if>
        <if test="nickName != null and nickName != ''">nick_name,</if>
        <if test="email != null and email != ''">email,</if>
        <if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
        <if test="sex != null and sex != ''">sex,</if>
        <if test="avatar != null and avatar != ''">avatar,</if>
        <if test="password != null and password != ''">password,</if>
        <if test="status != null and status != ''">status,</if>
        <if test="createBy != null and createBy != ''">create_by,</if>
        <if test="createTime != null">create_time,</if>
        <if test="remark != null">remark,</if>
    </trim>
    <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
        <if test="deptId != null">#{deptId},</if>
        <if test="userName != null and userName != ''">#{userName},</if>
        <if test="nickName != null and nickName != ''">#{nickName},</if>
        <if test="email != null and email != ''">#{email},</if>
        <if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
        <if test="sex != null and sex != ''">#{sex},</if>
        <if test="avatar != null and avatar != ''">#{avatar},</if>
        <if test="password != null and password != ''">#{password},</if>
        <if test="status != null and status != ''">#{status},</if>
        <if test="createBy != null and createBy != ''">#{createBy},</if>
        <if test="createTime != null">#{createTime},</if>
        <if test="remark != null">#{remark},</if>
    </trim>
</insert>

51. 常见完整示例:修改

<update id="updateUser" parameterType="SysUser">
    UPDATE sys_user
    <set>
        <if test="deptId != null">dept_id = #{deptId},</if>
        <if test="userName != null and userName != ''">user_name = #{userName},</if>
        <if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
        <if test="email != null and email != ''">email = #{email},</if>
        <if test="phonenumber != null and phonenumber != ''">phonenumber = #{phonenumber},</if>
        <if test="sex != null and sex != ''">sex = #{sex},</if>
        <if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
        <if test="password != null and password != ''">password = #{password},</if>
        <if test="status != null and status != ''">status = #{status},</if>
        <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
        <if test="updateTime != null">update_time = #{updateTime},</if>
        <if test="remark != null">remark = #{remark},</if>
    </set>
    WHERE user_id = #{userId}
</update>

52. 常见完整示例:批量删除

<delete id="deleteUserByIds">
    DELETE FROM sys_user
    WHERE user_id IN
    <foreach collection="array" item="userId" open="(" separator="," close=")">
        #{userId}
    </foreach>
</delete>

Mapper:

int deleteUserByIds(Long[] userIds);

53. 常见完整示例:逻辑删除

<update id="deleteUserById">
    UPDATE sys_user
    SET del_flag = '2'
    WHERE user_id = #{userId}
</update>

54. MyBatis XML 标签总表

标签 作用
<mapper> 根标签,绑定 Mapper 接口
<select> 查询
<insert> 新增
<update> 修改
<delete> 删除
<resultMap> 手动映射结果
<sql> 定义 SQL 片段
<include> 引用 SQL 片段
<if> 条件判断
<where> 动态 WHERE
<set> 动态 SET
<trim> 自定义前后缀、去多余字符
<foreach> 遍历集合
<choose> 多分支判断
<when> choose 的分支
<otherwise> choose 的默认分支

55. 最重要的记忆口诀

select 查数据,要写 resultType 或 resultMap
insert 新增,多字段用两个 trim
update 修改,多字段用 set
delete 删除,一定小心 WHERE
if 判断 Java 属性,不是数据库字段
#{ } 取参数值,安全
${ } 拼字符串,有风险
where 自动处理 WHERE 和 AND
foreach 专门处理 IN 和批量
sql + include 用来复用字段片段

56. 最核心主线

你可以按这个流程理解 MyBatis XML:

前端传参数

Controller 接收

Service 调 Mapper

Mapper 接口方法接收 Java 参数

XML 通过 #{xxx} 取参数

<if test=""> 判断参数有没有值

动态拼 SQL

数据库执行

查询结果通过 resultType/resultMap 封装成 Java 对象

返回给前端

57. 对你最实用的复习顺序

先掌握这几个:

1. select + resultType
2. insert + parameterType
3. update + set
4. if test
5. trim
6. where
7. foreach
8. resultMap
9. sql + include
10. #{} 和 ${} 区别

最常见的就是:

<select>
<insert>
<update>
<delete>
<if>
<where>
<set>
<trim>
<foreach>
<sql>
<include>

SQL + MyBatis 速查手册

一、 基础查询 (The Basics)

场景: 从数据库拿数据。

  • 全量查询SELECT * FROM sys_user;
  • 指定字段(推荐):SELECT user_name, nick_name FROM sys_user;
  • 起别名(解决字段冲突):SELECT user_name AS name FROM sys_user;
  • 去重SELECT DISTINCT status FROM sys_user;

二、 条件过滤 (WHERE)

场景: 筛选特定的行。

  • 等于/不等于WHERE status = '0' / WHERE status != '1'
  • 模糊搜索WHERE user_name LIKE '%张%' (包含张)
  • 范围查询WHERE age BETWEEN 18 AND 30
  • 集合查询WHERE dept_id IN (1, 2, 3)
  • 判空WHERE email IS NULL / WHERE email IS NOT NULL
  • 逻辑组合WHERE status = '0' AND (age > 18 OR sex = '1')

三、 排序与分页 (Order & Limit)

场景: 列表展示逻辑。

  • 排序ORDER BY create_time DESC (DESC 降序/最新,ASC 升序/最早)
  • 分页 (MySQL)LIMIT 0, 10 (跳过 0 条,取 10 条,即第 1 页)

四、 多表关联 (Joins)

场景: 数据分布在多张表。

  • LEFT JOIN (最常用):保留左表全部,右表没匹配上补 NULL。ON是两张表的关联条件。

    SELECT u.user_name, d.dept_name
    FROM sys_user u
    LEFT JOIN sys_dept d ON u.dept_id = d.dept_id;
  • INNER JOIN:只保留两表都能匹配上的行。

    SELECT u.user_name, d.dept_name
    FROM sys_user u
    INNER JOIN sys_dept d ON u.dept_id = d.dept_id;

    sys_user 用户表:

    user_id user_name dept_id
    1 张三 100
    2 李四 101
    3 王五 999

    sys_dept 部门表:

    dept_id dept_name
    100 技术部
    101 财务部

    结果是:

    user_name dept_name
    张三 技术部
    李四 财务部

    王五的 dept_id = 999 但是 sys_dept 表里没有 dept_id = 999,所以 INNER JOIN 会把王五过滤掉。

五、 MyBatis XML 动态标签

场景: 接口开发,根据前端传参动态生成 SQL。

1. <if> 标签(最核心)

XML

<if test="userName != null and userName != ''">
    AND user_name LIKE concat('%', #{userName}, '%')
</if>
  • String 类型:必须判断 != null!= ''
  • Number 类型:只判断 != null

2. <where> 标签

自动处理第一个 ANDOR,防止语法错误。

XML

<where>
    <if test="..."> AND ... </if>
</where>

3. <foreach> 标签(批量操作)

用于 IN 查询或批量插入。

XML

WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
    #{id}
</foreach>

六、 数据变更 (DML)

注意:UPDATE 和 DELETE 必须带 WHERE!

  • 新增INSERT INTO table (col1, col2) VALUES (val1, val2);
  • 修改UPDATE sys_user SET status = '0' WHERE user_id = 1;
  • 逻辑删除(推荐):UPDATE sys_user SET del_flag = '2' WHERE user_id = 1;

七、 常用函数 (MySQL)

  • 统计总数COUNT(*)
  • 字符串拼接CONCAT('%', '关键词', '%')
  • 空值转换IFNULL(column, '默认值')
  • 日期格式化DATE_FORMAT(create_time, '%Y-%m-%d')

注意指南

  1. 单引号:SQL查询时值要加单引号 WHERE user_name = 'admin';,数字类型不要加。
  2. 别名:给表起别名(如 sys_user u)可以防止多表联查时字段名冲突(如两个表都有 id)。
  3. 转义字符:在 XML 里写大于小于号要转义:
    • <&lt;
    • >&gt;
    • >=&gt;=

© 2026 转载请注明出处