让 SQL 像 Java 一样有 if-else —— 按条件自动拼接
打开京东搜索页面,你可以按关键词搜、按价格区间筛、按品牌筛、按销量排序...
用户可能只填了其中 1 个条件,也可能填了 5 个条件。
如果没有动态 SQL,你得写多少种 SQL?
WHERE name LIKE ?WHERE name LIKE ? AND price > ?WHERE name LIKE ? AND brand = ?WHERE name LIKE ? AND price > ? AND brand = ? ...请先完成第 1 章的项目搭建(Spring Boot + MyBatis + MySQL)。本章在第 1 章的基础上修改已有文件。
// 📄 在 UserMapper.java 中增加方法:
List<User> searchUsers(@Param("username") String username,
@Param("minAge") Integer minAge,
@Param("maxAge") Integer maxAge);
// 📄 在 UserController.java 中增加接口:
@GetMapping("/search")
public List<User> search(@RequestParam(required=false) String username,
@RequestParam(required=false) Integer minAge,
@RequestParam(required=false) Integer maxAge) {
return userMapper.searchUsers(username, minAge, maxAge);
}# 📄 启动项目后,在浏览器或 Postman 中测试:
# 启动:在 IDEA 中运行 Application.java 的 main 方法
# 只按用户名搜索
GET http://localhost:8080/search?username=张
# 只按年龄范围搜索
GET http://localhost:8080/search?minAge=25&maxAge=30
# 组合搜索
GET http://localhost:8080/search?username=张&minAge=20&maxAge=30
# 不传任何参数 → 查所有
GET http://localhost:8080/search
# 观察 IDEA 控制台打印的 SQL,看动态拼接的效果!
你对服务员说:"给我来份面"(基本 SQL)
如果你想加辣 → "要辣的"(拼上 AND spicy = true)
如果你要大碗 → "要大碗"(拼上 AND size = 'large')
如果你什么都不加 → 就是普通面(不加任何 WHERE 条件)
<!-- 📄 UserMapper.xml — 动态条件查询 -->
<!-- ❌ 错误写法:WHERE 1=1 丑陋又不专业 -->
<select id="findByCondition" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
</select>
<!-- ✅ 正确写法:用 <where> 标签! -->
<select id="findByCondition" resultType="User">
SELECT * FROM users
<!-- <where> 标签的两个魔法:
1. 只有内部有内容时才加 WHERE 关键字
2. 自动去掉开头多余的 AND / OR -->
<where>
<!-- test 里写 Java 表达式,判断参数是否有值 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
ORDER BY id DESC
</select>勾选不同的查询条件,实时看到 MyBatis 生成的 SQL:
场景:搜索用户时,优先按用户名搜,没有用户名就按邮箱搜,都没有就返回所有成年人。
<!-- choose/when/otherwise = Java 的 if-else if-else -->
<select id="findByPriority" resultType="User">
SELECT * FROM users
<where>
<choose>
<!-- 优先按用户名搜 -->
<when test="username != null">
username = #{username}
</when>
<!-- 用户名没填?那按邮箱搜 -->
<when test="email != null">
email = #{email}
</when>
<!-- 都没填?返回所有成年用户 -->
<otherwise>
age >= 18
</otherwise>
</choose>
</where>
</select>
<!-- 等同于 Java 代码:
if (username != null) {
sql += "WHERE username = ?";
} else if (email != null) {
sql += "WHERE email = ?";
} else {
sql += "WHERE age >= 18";
}
-->场景:购物车批量删除、批量导入用户、IN 查询等。
<!-- 📄 批量删除 — DELETE FROM users WHERE id IN (1, 3, 5) -->
<delete id="deleteByIds">
DELETE FROM users WHERE id IN
<!-- foreach 属性详解:
collection = 参数名(List 用 "list",数组用 "array",或自定义名 @Param)
item = 每一项的变量名
open = 循环开始前加的字符
close = 循环结束后加的字符
separator = 每项之间的分隔符 -->
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
<!-- 📄 批量插入 — 一次插入多条数据 -->
<insert id="batchInsert">
INSERT INTO users(username, email, age) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.email}, #{user.age})
</foreach>
</insert>场景:用户编辑个人资料,只改了昵称没改邮箱,SQL 就只更新昵称。
<!-- <set> 标签的两个魔法:
1. 自动加 SET 关键字
2. 自动去掉最后多余的逗号 -->
<update id="updateSelective">
UPDATE users
<set>
<!-- 只有传了 username 才更新 username 列 -->
<if test="username != null">
username = #{username},
</if>
<!-- 只有传了 email 才更新 email 列 -->
<if test="email != null">
email = #{email},
</if>
<!-- 只有传了 age 才更新 age 列 -->
<if test="age != null">
age = #{age},
</if>
</set>
WHERE id = #{id}
</update>
<!-- 如果只传了 username:
生成 SQL → UPDATE users SET username = '张三' WHERE id = 1
✅ <set> 自动去掉了最后那个多余的逗号! -->把所有标签串起来,看一个真实的搜索功能怎么写:
// 📄 UserMapper.java — 接口定义
@Mapper
public interface UserMapper {
// 动态条件查询(SQL 在 XML 中)
List<User> search(UserSearchParam param);
// 批量删除(SQL 在 XML 中)
int deleteByIds(@Param("ids") List<Long> ids);
// 选择性更新(SQL 在 XML 中)
int updateSelective(User user);
}// 📄 UserSearchParam.java — 搜索参数类
// 把所有搜索条件封装成一个对象,比一堆参数清晰
public class UserSearchParam {
private String username; // 按用户名搜(可选)
private String email; // 按邮箱搜(可选)
private Integer minAge; // 最小年龄(可选)
private Integer maxAge; // 最大年龄(可选)
private String orderBy; // 排序字段(可选)
// getter/setter 省略...
}<!-- 📄 UserMapper.xml — 完整的动态查询 -->
<select id="search" resultType="User">
SELECT * FROM users
<where>
<!-- 用户名模糊搜索 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<!-- 邮箱精确搜索 -->
<if test="email != null and email != ''">
AND email = #{email}
</if>
<!-- 年龄范围 -->
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</where>
<!-- 动态排序(⚠️ 排序字段用 ${} 因为是列名,不能用 #{}) -->
<if test="orderBy != null">
ORDER BY ${orderBy}
</if>
<if test="orderBy == null">
ORDER BY id DESC
</if>
</select>// 📄 UserController.java — 搜索接口
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserMapper userMapper;
// GET /api/users/search?username=张&minAge=20&maxAge=30
@GetMapping("/search")
public List<User> search(UserSearchParam param) {
// Spring 自动把 URL 参数绑定到 param 对象
return userMapper.search(param);
}
// DELETE /api/users/batch?ids=1,3,5
@DeleteMapping("/batch")
public String batchDelete(@RequestParam List<Long> ids) {
int count = userMapper.deleteByIds(ids);
return "成功删除 " + count + " 条记录";
}
}#{} = 预编译占位符 ?,防 SQL 注入,99% 情况用它${} = 字符串直接拼接,有注入风险,只用于列名、表名等不能用 ? 的地方