第一部分:动态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 |
元素间的分隔符 |