动态 SQL 主要是用来完成不同条件下的 SQL 拼接的,它可以通过一些例如 if 标签、where 标签等,来直接限制 SQL 语句的条件。
如果说 SQL 是 HTML 代码,那么动态 SQL 的标签就相当于 JS,它可以对 SQL 语句进行变换、更改、限制。
我们观察下面的 userinfo 表,发现 username 和 password 是必传参数,而 photo 是有默认值的。
所以,我们预期实现下面的两个测试。
第一个测试:插入 username、password、photo
带一个测试:插入 username 、password
那么,我们预期的第二个测试的 photo 的字段值为默认的 " 123.png ",然而,第一个测试就是我们自己插入而设置的 photo 值。
很显然,如果我们利用 MyBatis 写 SQL 语句,应该如下写:
-- 第一个测试
insert into userinfo (username, password, photo) values (#{username}, #{password}, #{photo})
-- 第二个测试
insert into userinfo (username, password) values (#{username}, #{password})
基于上述的过程,我们发现,在传统的 SQL 语句写法上,针对于两次测试,我们必须写两个 SQL 语句。这就带来一个问题,如果客户端需要改变需求了,那么后端程序员就要重写写 SQL,也就要重新改变 xml 文件中的一些配置,这会很麻烦…
但实际上,如果我们利用 if 标签,就能够直接对 photo 这个字段进行限制,从而只需要写一个 SQL 语句即可。根据一个 SQL 语句,再加上一些限制标签,就能够很好地解决相似的问题。
综上所述,我们利用 MyBatis 为我们提供的标签来实现动态 SQL,会带来一个灵活的功能:后端在传 SQL 参数时,能够方便地控制增删改查的条件了。
if 标签常用来判断一个参数是否被需要,如果参数不需要,就会隐藏 SQL.
UserInfo 类:
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
}
" UserMapper " 接口:
@Mapper
public interface UserMapper {
// 添加新用户,使用动态 SQL 中的 if 标签
public int addUser2(UserInfo userInfo);
}
xml 文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id="addUser2">
insert into userinfo (username, password
<if test="photo != null">
,photo
if>
)values (#{username}, #{password}
<if test="photo != null">
,#{photo}
if>
)
insert>
mapper>
测试类1:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 添加新用户,使用动态 SQL 中的 if 标签
@Test
void addUser2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("JJ");
userInfo.setPassword("246");
userInfo.setPhoto("JJ.png");
int result = userMapper.addUser2(userInfo);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
测试类2:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 添加新用户,使用动态 SQL 中的 if 标签
@Test
void addUser2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("云云");
userInfo.setPassword("137");
int result = userMapper.addUser2(userInfo);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
if 标签中的 test 是必传参数,传的是对象的属性。
<if test="">
</if>
如下图所示,if 标签中的值既可以写表的字段,也可以写对象的属性,通常情况下,这两者就是用来相互配合的,我们应该弄清楚两者的区别。
此外,如果真的分不清楚两者的关系,我们就可以将实体类的成员变量和数据表的字段写成一样的名字,这样就可以无差别对待了。
trim 标签一般和 if 标签搭配使用,它能够为 SQL 语句添加或去除某个字符(串)。
UserInfo 类:
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
}
" UserMapper " 接口:
@Mapper
public interface UserMapper {
// 添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签
public int addUser3(UserInfo userInfo);
}
xml 文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id="addUser3">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
username,
if>
<if test="password != null">
password,
if>
<if test="photo != null">
photo
if>
trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
#{username},
if>
<if test="password != null">
#{password},
if>
<if test="photo != null">
#{photo}
if>
trim>
insert>
mapper>
测试类1:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签
@Test
void addUser3() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("莉莉");
userInfo.setPassword("632");
int result = userMapper.addUser3(userInfo);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
测试类2:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 添加新用户,使用动态 SQL 中的 if 标签 和 trim 标签
@Test
void addUser3() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("杰杰");
userInfo.setPassword("168");
userInfo.setPhoto("杰杰.png");
int result = userMapper.addUser3(userInfo);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
测试类1 的 SQL 转换,即去除 " 逗号 " 的过程,如下图所示:
此外,trim 标签非常智能,它能够自动检测 SQL 语句块的末尾是否真的有 " 逗号 ",例如我们上面的测试类2 中,我们实现了 【 suffixOverrides = “,” 】,但 photo 的末尾是没有 " 逗号 "的,也不会报错。如下所示:
insert into userinfo (username, password, photo) values (#{username}, #{password}, #{photo})
trim 标签有四个属性,它们可以达到往 SQL 语句块中拼接或删除字符(串)的作用,从而控制 SQL 的写法。四个属性不是必须的,可以根据语法自行设置。
① prefix:需要拼接的前缀字符串
② suffix:需要拼接的后缀字符串
③ prefixOverrides:需要删除的前缀字符串
④ suffixOverrides:需要删除的后缀字符串
where 标签一般与 if 标签搭配使用,它可以直接代替 SQL 语句中的 where 关键字,在 where 标签中,我们可以控制条件的数量,从而达到不同的 select 查询要求。
UserInfo 类:
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
}
" UserMapper " 接口:
@Mapper
public interface UserMapper {
// 查询用户使用 where 标签
public List<UserInfo> getUserById2(@Param("id") Integer id);
}
xml 文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="getUserById2" resultType="com.example.demo.model.UserInfo">
select * from userinfo
<where>
<if test="id != null">
id = #{id}
if>
where>
select>
mapper>
测试类1:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 查询用户使用 where 标签
@Test
void getUserById2() {
List<UserInfo> list = userMapper.getUserById2(2);
System.out.println("测试结果:" + list);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
测试类2:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 查询用户使用 where 标签
@Test
void getUserById2() {
List<UserInfo> list = userMapper.getUserById2(null);
System.out.println("测试结果:" + list);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
如果 where 标签中的有值,那么就会正常执行 where 后面的 SQL 语句,如果 where 标签中没有值,那么整个 where 语句也会被省略。所以一般情况下,where 被省略了,就会变成一个全列查询。
此外,where 标签也等价于下面的写法,实际上它能省略语句中的前缀 and,但是它省略不了语句中的后缀 and.
<trim prefix="where" prefixOverrides="and">
trim>
基于上述的 where 语法,形如下面的代码,一个 SQL 语句中,我们就可以变幻出四种语法。
select * from userinfo
<where>
<if test="username!= null">
username= #{usernname}
if>
<if test="password!= null">
and password= #{password}
if>
where>
① 只查询 username
② 只查询 password
③ 既查询 username,也查询 password
④ 全列查询
set 标签一般与 if 标签搭配使用,它可以直接代替 SQL语句中的 set 关键字,在 set 标签中,我们可以控制修改字段的数量,从而达到不同的 update 修改要求。
UserInfo 类:
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
}
" UserMapper " 接口:
@Mapper
public interface UserMapper {
// 修改用户使用 set 标签
public int update2(UserInfo userInfo);
}
xml 文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<update id="update2">
update userinfo
<set>
<if test="username != null">
username = #{username},
if>
<if test="password != null">
password = #{password},
if>
<if test="photo != null">
photo = #{photo}
if>
set>
where id = #{id}
update>
mapper>
测试类1:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 修改用户使用 set 标签
@Test
void update2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("王五");
userInfo.setPassword("777");
userInfo.setPhoto("777.png");
userInfo.setId(7);
int result = userMapper.update2(userInfo);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
测试类2:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 修改用户使用 set 标签
@Test
void update2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("王五");
userInfo.setPassword("777");
userInfo.setPhoto("777.png");
userInfo.setId(7);
int result = userMapper.update2(userInfo);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
一般来说,set 用于 update 修改操作中,所以,既然要修改了,那么在 set 标签中,必然是需要至少一个值,否则就会报错。
此外,set 标签也等价于下面的写法,实际上它能省略语句中的后缀 " 逗号 ",但是它省略不了语句中的前缀 " 逗号 " .
<trim prefix="set" suffixOverrides=",">
trim>
此外,它也很智能,它能够自动检测 SQL 语句块的末尾是否真的有 " 逗号 ",对于末尾没有逗号的情况,它不会执行删除操作,同时也不会报错。
foreach 标签一般与 SQL 中的 in() 关键字搭配,可以做到批量化操作。
下面的代码就是展示了批量删除的操作。
UserInfo 类:
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
}
" UserMapper " 接口:
@Mapper
public interface UserMapper {
// 批量删除使用 foreach 标签
public int deleteByIds(List<Integer> ids);
}
xml 文件
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<delete id="deleteByIds">
delete from userinfo where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
foreach>
delete>
mapper>
测试类1:
@SpringBootTest
class UserMapperTest {
@Resource
private UserMapper userMapper;
// 批量删除使用 foreach 标签
@Test
void deleteByIds() {
List<Integer> ids = new ArrayList<>();
// 添加需要删除的用户 id
ids.add(8);
ids.add(9);
ids.add(10);
int result = userMapper.deleteByIds(ids);
System.out.println("测试结果: " + result);
}
}
启动测试方法,查看 MyBatis 日志打印:
观察数据库:
foreach 标签的语法可以类比于 Java 中 for each 遍历数组的用法。
foreach 标签常用的有五个属性,它们可以达到往 SQL 语句块中拼接或删除字符(串)的作用,从而控制 SQL 的写法。
① collection:一组相同的数据的集合,类似于数组,
这里需要注意,collection 中的值需要与前端传来的参数匹配。
② item:集合中的一项数据,类似于数组中的某一个元素
③ open:需要拼接的前缀字符(串)
④ close:需要拼接的后缀字符(串)
⑤ separator:一组数据的分隔符
动态 SQL 就和它的名字一样,赋予了 SQL 一个动态的语法,我们可以根据前端需要的参数,来控制一整条 SQL 语句。本质上,关于动态 SQL 的标签,就是用来拼接或删除 SQL 的,没有其他的作用。
在使用动态 SQL 之前,我们应该先把心中设想好的完完整整的 SQL 写出来,之后再对此 SQL 进行变换,这样一来,便不会出错。