第一部分:动态SQL概述
1.1 为什么需要动态SQL
⭐老师强调:之前在Eclipse里查学生,是在Java中用
if-else硬拼接SQL——传名字、性别或班级就手动加条件和空格,很麻烦。现在用动态SQL不用再自己复杂拼接了。
| 对比 | 硬拼接 | 动态SQL |
|---|---|---|
| 实现方式 | 代码里人工判断并拼字符串 | 框架按条件自动处理 |
| 易错点 | 容易漏空格、多and/or | 框架自动处理 |
| 维护性 | 麻烦 | 简洁 |
⭐老师强调:硬拼接是人工判断并拼字符串,易漏空格出错;动态SQL由框架按条件自动处理,更简洁。两者内在联系是都为实现多条件查询,但实现方式从手动变自动。
1.2 参数设计优化
⭐老师强调:原本按姓名、性别、年龄分别传参,但接口强制要求三个参数都必须传,调用者不传就编不过。
问题:想按姓名查也得凑齐性别、年龄。
解决:改用传一个Student对象代替多个参数——对象里的字段可随意设,没值的就不填。
// ❌ 死板写法:必须传三个参数 List<Student> search(String name, String sex, Integer age); // ✅ 灵活写法:传对象,按需设置字段 List<Student> searchStudent(Student student);⭐老师强调:传参时若传入
Student类型数据,该对象本身肯定传了,但像名字、性别这类字段有无值取决于是否显式设置——设置了才有,没设置就是null。
第二部分:if 标签
2.1 问题场景
⭐老师强调:用实体类查学生时,若参数是null(如age为null),直接拼SQL会写成
where age = null,找不到数据,返回零,拼接出错。
问题SQL:
select * from student where name = ? and sex = ? and age = ? -- 若age为null,变成 where age = null,查不到2.2 if 标签用法
<select id="searchStudent" parameterType="com.qcby.entity.Student" resultType="com.qcby.entity.Student"> select * from student where <if test="name!=null and name!=''"> name = #{name} </if> <if test="sex!=null and sex!=''"> and sex = #{sex} </if> <if test="age!=null and age!=''"> and age = #{age} </if> </select>⭐老师强调:
if标签类似Java的if判断,但不能写if(条件),而是用test属性写条件。非空才把对应片段拼进语句。
2.3 if 标签的问题
⭐老师强调:若不传name只传sex,拼出
where and sex=?,where后直接跟and不合理,SQL语法出错。
问题:动态拼接要注意首条件不能带and。
⭐老师强调:动态拼接要注意首条件不能带and,常需额外处理(如where 1=1或trim前缀)。拼接逻辑对但语义错——不传的字段不进条件没错,但剩余条件若以and开头就破坏语句结构。
第三部分:where 标签
3.1 where if 标签
<select id="searchStudent" parameterType="com.qcby.entity.Student" resultType="com.qcby.entity.Student"> select * from student <where> <if test="name!=null and name!=''"> name = #{name} </if> <if test="sex!=null and sex!=''"> and sex = #{sex} </if> <if test="age!=null and age!=''"> and age = #{age} </if> </where> </select>⭐老师强调:
where标签的好处是能按语义自动剔除前方多余and/or——像只传年龄时,它自己把sex前的and裁掉。这类似智能修剪枝叶,只留有效条件,避免语法错。
效果对比:
| 传参 | 生成SQL |
|---|---|
| 只传name | where name = ? |
| 传sex和age | where sex=? and age=? |
| 传name、sex、age | where name=? and sex=? and age=? |
第四部分:trim 标签
4.1 trim 替代 where
<select id="searchStudent" parameterType="com.qcby.entity.Student" resultType="com.qcby.entity.Student"> select * from student <trim prefix="where" prefixOverrides="and | or"> <if test="name!=null and name!=''"> name = #{name} </if> <if test="sex!=null and sex!=''"> and sex = #{sex} </if> <if test="age!=null and age!=''"> and age = #{age} </if> </trim> </select>4.2 trim 属性说明
| 属性 | 作用 |
|---|---|
prefix | 前缀,加在整体前面(如where) |
prefixOverrides | 去掉第一个and或or |
suffix | 后缀 |
suffixOverrides | 去掉最后一个符号(如逗号) |
⭐老师强调:
trim标签是更通用的标记,能代替set或where标签。prefixOverrides是去掉每句话前边的and/or,suffixOverrides是去掉句末符号。前后去什么都可配置。
第五部分:choose-when-otherwise 标签
5.1 语法结构
<select id="searchStudent" parameterType="com.qcby.entity.Student" resultType="com.qcby.entity.Student"> select * from student <where> <choose> <when test="name!=null and name!=''"> name = #{name} </when> <when test="sex!=null and sex!=''"> and sex = #{sex} </when> <when test="age!=null and age!=''"> and age = #{age} </when> <otherwise> and id = 28 </otherwise> </choose> </where> </select>5.2 与Java if-else对比
| MyBatis | Java | 说明 |
|---|---|---|
choose | 整体分支结构 | 包裹所有条件 |
when | if/else if | 第一个when是if,后续是else if |
otherwise | else | 都不满足时执行 |
⭐老师强调:程序会从上到下依次匹配条件——先判断第一个条件,满足就只进该分支;不满足再判断第二个;三个都不满足才走
otherwise分支。命中即止,和if-else if一样。
示例:
| 传参 | 执行的SQL |
|---|---|
| 只传age=38 | where age = 38 |
| 传name和age | 匹配到name分支,where name = ? |
| 都不传 | 走otherwise,where id = 28 |
第六部分:set 标签(动态修改)
6.1 问题场景
⭐老师强调:上午写法是
set name=?, age=?, sex=? where id=?来做修改。三个参数都得传;若少传一个如只改性别,其余字段拼接到SQL里会是空值,把原数据清空了。
问题:只想改年龄或性别,却清空了名字等原有信息。
6.2 set if 标签
<update id="updateStudent" parameterType="com.qcby.entity.Student"> update student <set> <if test="name!=null and name!=''"> name = #{name}, </if> <if test="sex!=null and sex!=''"> sex = #{sex}, </if> <if test="age!=null and age!=''"> age = #{age} </if> </set> where id = #{id} </update>⭐老师强调:
set会自动去掉末尾多余逗号,按实际传参情况决定去留。这样能灵活按需更新,不误清未传字段。
6.3 trim 替代 set
<update id="updateStudent" parameterType="com.qcby.entity.Student"> update student <trim prefix="set" suffixOverrides=","> <if test="name!=null and name!=''"> name = #{name}, </if> <if test="sex!=null and sex!=''"> sex = #{sex}, </if> <if test="age!=null and age!=''"> age = #{age} </if> </trim> where id = #{id} </update>⭐老师强调:
trim既能做前缀也能做后缀,可当作一堆if条件的前缀来用。代替set时,prefix="set"加前缀,suffixOverrides=","去掉末尾逗号。
6.4 修改注意事项
⭐老师强调:
- 必须写id条件:修改时必须写
where id=?,否则会误改全表数据- 参数莫漏:漏传字段会导致置空
- 提交事务:改完要提交事务才生效
第七部分:foreach 标签(批量操作)
7.1 批量删除
接口:
int deleteStudent(@Param("ids") Integer[] ids);映射文件:
<delete id="deleteStudent"> delete from student where id in <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </delete>foreach属性说明:
| 属性 | 作用 | 示例 |
|---|---|---|
collection | 要循环的数组或集合 | ids |
item | 数组中的每一个元素 | id |
open | 循环开始 | ( |
close | 循环结束 | ) |
separator | 每个元素用什么隔开 | , |
生成的SQL:
delete from student where id in (5, 6, 7)⭐老师强调:
open/close是整体循环的头尾包装,只出现一次separator是每两个元素之间才插入,不参与头尾- 多参数或复杂参数必须加
@Param注解才能注入
7.2 批量添加
接口:
int insertStudents(@Param("students") List<Student> students);映射文件:
<insert id="insertStudents"> insert into student(name,age,sex) values <foreach collection="students" item="stu" separator=","> (#{stu.name},#{stu.age},#{stu.sex}) </foreach> </insert>生成的SQL:
insert into student(name,age,sex) values ('lili456',20,'女'),('lucy456',28,'男'),('tony456',32,'女'),('davi456',21,'男')⭐老师强调:
- 批量添加本质就是单条insert语句里values后多组括号,用逗号隔开
foreach遍历集合,每项拼出一组(#{stu.name},#{stu.age},#{stu.sex})- 拼的时候拿集合每一项,用逗号隔开依次造结果,如"项1,项2,项3"
- 加括号、逗号等修饰都行
第八部分:完整代码汇总
8.1 实体类(Student.java)
package com.qcby.entity; public class Student { private Integer id; private String name; private Integer age; private String sex; public Student() {} public Student(String name, Integer age, String sex) { this.name = name; this.age = age; this.sex = sex; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }8.2 接口(StudentDao.java)
package com.qcby.dao; import com.qcby.entity.Student; import org.apache.ibatis.annotations.Param; import java.util.List; public interface StudentDao { // 动态查找 List<Student> searchStudent(Student student); // 动态修改 int updateStudent(Student student); // 批量删除 int deleteStudent(@Param("ids") Integer[] ids); // 批量添加 int insertStudents(@Param("students") List<Student> students); }8.3 映射文件(StudentDao.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.qcby.dao.StudentDao"> <!-- where if 动态查找 --> <select id="searchStudent" parameterType="com.qcby.entity.Student" resultType="com.qcby.entity.Student"> select * from student <where> <if test="name!=null and name!=''"> name = #{name} </if> <if test="sex!=null and sex!=''"> and sex = #{sex} </if> <if test="age!=null and age!=''"> and age = #{age} </if> </where> </select> <!-- trim 动态修改 --> <update id="updateStudent" parameterType="com.qcby.entity.Student"> update student <trim prefix="set" suffixOverrides=","> <if test="name!=null and name!=''"> name = #{name}, </if> <if test="sex!=null and sex!=''"> sex = #{sex}, </if> <if test="age!=null and age!=''"> age = #{age} </if> </trim> where id = #{id} </update> <!-- foreach 批量删除 --> <delete id="deleteStudent"> delete from student where id in <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </delete> <!-- foreach 批量添加 --> <insert id="insertStudents"> insert into student(name,age,sex) values <foreach collection="students" item="stu" separator=","> (#{stu.name},#{stu.age},#{stu.sex}) </foreach> </insert> </mapper>8.4 测试类(StudentTest.java)
package com.qcby; import com.qcby.dao.StudentDao; import com.qcby.entity.Student; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class StudentTest { private InputStream inputStream = null; private SqlSession session = null; private StudentDao mapper = null; @Before public void init() throws IOException { inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); session = sqlSessionFactory.openSession(); mapper = session.getMapper(StudentDao.class); } @After public void destroy() throws IOException { session.close(); inputStream.close(); } // 动态查找 @Test public void run() { Student student = new Student(); // student.setName("张三"); // student.setAge(38); // student.setSex("女"); List<Student> students = mapper.searchStudent(student); for (Student stu : students) { System.out.println(stu); } } // 动态修改 @Test public void update() { Student student = new Student(); // student.setName("张三"); // student.setAge(38); student.setSex("男"); student.setId(28); int code = mapper.updateStudent(student); session.commit(); System.out.println(code); } // 批量删除 @Test public void delete() { int code = mapper.deleteStudent(new Integer[]{5, 6, 7}); session.commit(); System.out.println(code); } // 批量添加 @Test public void insert() { Student student1 = new Student("lili456", 20, "女"); Student student2 = new Student("lucy456", 28, "男"); Student student3 = new Student("tony456", 32, "女"); Student student4 = new Student("davi456", 21, "男"); List<Student> students = new ArrayList<Student>(); students.add(student1); students.add(student2); students.add(student3); students.add(student4); int code = mapper.insertStudents(students); session.commit(); System.out.println(code); } }附录一:动态SQL标签速查表
| 标签 | 作用 | 典型场景 |
|---|---|---|
<if> | 条件判断,非空才拼 | 动态查询条件 |
<where> | 自动处理where和多余的and/or | 查询条件拼接 |
<trim> | 通用格式化标签,可替代where/set | 灵活定制前后缀 |
<choose>/<when>/<otherwise> | 多选一分支 | if-else if-else |
<set> | 动态更新,自动去末尾逗号 | 修改操作 |
<foreach> | 遍历数组/集合 | 批量删除、批量添加 |
trim属性速查
| 属性 | 作用 |
|---|---|
prefix | 整体前缀 |
prefixOverrides | 去掉第一个and/or |
suffix | 整体后缀 |
suffixOverrides | 去掉末尾符号 |
foreach属性速查
| 属性 | 作用 |
|---|---|
collection | 要循环的数组或集合 |
item | 每个元素的临时名 |
open | 循环开始符号 |
close | 循环结束符号 |
separator | 元素间的分隔符 |