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
自动去掉最后多余的逗号
比如只传了 userName 和 email,最终 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 查询一般不能省略 resultType 或 resultMap。
错误示例:
<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 >= #{beginTime}
</if>
金额
<if test="amount != null">
AND amount = #{amount}
</if>
集合
<if test="ids != null and ids.size() > 0">
...
</if>
20. XML 里的特殊符号
XML 中不能直接写 <,否则会被当成标签。
所以要转义。
| SQL 符号 | XML 写法 |
|---|---|
< |
< |
<= |
<= |
> |
> |
>= |
>= |
& |
& |
比如:
<if test="beginTime != null">
AND create_time >= #{beginTime}
</if>
<if test="endTime != null">
AND create_time <= #{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 >= #{beginTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
如果参数放在 params 里,若依常见:
<if test="params.beginTime != null and params.beginTime != ''">
AND create_time >= #{params.beginTime}
</if>
<if test="params.endTime != null and params.endTime != ''">
AND create_time <= #{params.endTime}
</if>
31. 若依里的 params 是什么?
若依很多实体继承了 BaseEntity,里面有:
private Map<String, Object> params;
所以 XML 里经常看到:
<if test="params.beginTime != null and params.beginTime != ''">
AND create_time >= #{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>
查询必须写 resultType 或 resultMap。
错误二:字段名和属性名混淆
错误:
<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 <= #{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> 标签
自动处理第一个 AND 或 OR,防止语法错误。
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')
注意指南
- 单引号:SQL查询时值要加单引号
WHERE user_name = 'admin';,数字类型不要加。 - 别名:给表起别名(如
sys_user u)可以防止多表联查时字段名冲突(如两个表都有id)。 - 转义字符:在 XML 里写大于小于号要转义:
<→<>→>>=→>=