文章目录
7.1. 遇到的问题
客户端查询的信息不确定,因此查询条件不确定,需要动态判断
- 示例:查询条件的不确定
以前的写法:
- 编写mapper接口(dao中的方法)
- 在mapper.xml编写SQL片段(statement)
- 测试
- mapper接口
//多条件查询
public List<User> selectByNameAndBirthday(User user);
- mapper.xml
<!--多条件查询-->
<select id="selectByNameAndBirthday" resultType="user">
select <include refid="baseColumn"></include>
from `user`
where username like #{username}
AND birthday = #{birthday}
</select>
- 测试
/**
* 多条件查询
*/
@Test
public void selectByNameOrPhone() throws ParseException {
//使用工具类获取sqlSession
SqlSession sqlSession = MyBatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//模拟接收到的前台参数
String username = "张";
Date birthday = new SimpleDateFormat("yyyy-MM-dd")
.parse("1998-12-25");
User user = new User();
user.setUsername("%"+username+"%");
//user.setBirthday(birthday);
//根据条件查询
List<User> users = mapper.selectByNameAndBirthday(user);
for (User u : users) {
System.out.println(u);
}
//关闭会话
MyBatisUtils.closeSession();
}
- 问题:
当没有输入全部查询条件时,反而查询不到结果,可以从日志的SQL语句中找到原因
==> Preparing:
select uid,username,birthday,phone,sex,address from `user` where username like ? AND birthday = ?
[DEBUG] 2020-07-19 23:01:26,937 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137)
==> Parameters: %张%(String), null
没有传入参数时,参数值为null,对应SQL如下:
select uid,username,birthday,phone,sex,address from `user` where username like " %张%" AND birthday = null
AND birthday = null
肯定查询不到结果,这时就需要动态判断参数是否有值,动态拼接SQL,下面我们就来学习学习MyBatis提供的动态SQL功能。
7.2. 动态SQL
动态 SQL 是 MyBatis 的强大功能之一,可以完成各种复杂的SQL,动态SQL 相关元素如下:
- if
- where
- choose(when otherwise)
- foreach
- set(了解)
- trim(了解)
7.2.1. if
if:对传入参数的属性值进行判断,结果为true则拼接SQL片段,否则不拼接
修改前面根据用户名和手机号查询的SQL片段
- mapper.xml
<!--动态SQL - if -->
<select id="selectByNameAndBirthday2" resultType="user">
select <include refid="baseColumn"></include>
from `user`
where
<if test="username != null and username != ''">
username like #{username}
</if>
<if test="birthday != null">
AND birthday = #{birthday}
</if>
</select>
这里使用if判断,如果用户输入了username、birthday,则该属性值!=null,拼接对应的查询SQL,否则不拼接
- 注意
if判断中,条件使用 and 、or拼接,不能使用 & | AND OR
非字符串类型不要进行空串比较,pro != ‘’,因为会先将pro强制转为字符串
- mapper接口
public List<User> selectByNameAndBirthday2(User user);
- 测试
/**
* 动态sql - if
*/
@Test
public void selectByNameOrPhone2() throws ParseException {
//使用工具类获取sqlSession
SqlSession sqlSession = MyBatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//模拟接收到的前台参数
String username = "张";
Date birthday = new SimpleDateFormat("yyyy-MM-dd")
.parse("1998-12-25");
User user = new User();
//user.setUsername("%"+username+"%");
user.setBirthday(birthday);
//根据条件查询
List<User> users = mapper.selectByNameAndBirthday2(user);
for (User u : users) {
System.out.println(u);
}
//关闭会话
MyBatisUtils.closeSession();
}
测试:输入birthday 结果:异常,对应SQL
SELECT uid,username,birthday,phone,sex,address FROM `user` WHERE AND birthday = ?
测试:不输入参数 结果 : 异常,对应SQL
SELECT uid,username,birthday,phone,sex,address FROM `user` WHERE
问题
- 有where条件时,加上where关键字,并自动判断要不要加and
- 希望没有条件时,不要where关键字
7.2.2. where
使用where元素解决上述问题
<!--动态SQL - if where -->
<select id="selectByNameAndBirthday2" resultType="user">
select <include refid="baseColumn"></include>
from `user`
<where>
<if test="username != null and username != ''">
username like #{username}
</if>
<if test="birthday != null">
AND birthday = #{birthday}
</if>
</where>
</select>
7.2.3. choose
choose相当于java中的switch
- 需求
- 根据性别查询,注意问题,用户输入的是“男”“女”,数据库中国存储的是1和2表示男女
- mapper接口
//动态sql - choose
public List<User> dynamicSQLChoose(String sex);
- mapper.xml
<select id="dynamicSQLChoose" resultType="user">
select
<include refid="baseColumn"></include>
from `user`
<where>
<choose>
<when test='sex != null and sex == "男"'>
and sex = '1'
</when>
<when test='sex != null and sex == "女"'>
and sex = '2'
</when>
<otherwise>
and sex is not null
</otherwise>
</choose>
</where>
</select>
- 这里必须注意:当比较的值只有一个字符时,mybatis会把值当成char处理,转成数值,汉字是无法直接转成数值的,因此这里要用双引号,表示是字符串
<when test='sex != null and sex == "女"'>
- 测试
/**
* 动态SQL choose
*/
@Test
public void dynamicSQLChoose(){
SqlSession sqlSession = MyBatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.dynamicSQLChoose("女");
for (User user : list) {
System.out.println(user);
}
//关闭会话
MyBatisUtils.closeSession();
}
7.2.4. foreach
类似于java中for循环,主要用于in语句的条件查询
需求:根据多个ID进行查询
- UserMapper.xml
<!--
SELECT uid,username,birthday,sex,address
FROM `user`
WHERE uid IN (1,10,12,16)
collection: 遍历的集合类型 arrar -> 数组 list -> list collection -> set
item ; 变量名,可以自定义,表示遍历的每一项的值
open: 开始内容
separator: 分隔符
close: 结束内容
-->
<select id="dynamicSQLForeach" resultType="user">
select uid,username,birthday,sex,address
from `user`
<if test="collection != null">
<where>
<foreach collection="collection" item="item" open="uid IN (" separator="," close=")">
#{item}
</foreach>
</where>
</if>
</select>
- mapper接口
public List<User> dynamicSQLForeach(Set<Integer> ids);
- 测试
@Test
public void dynamicSQLForeach(){
//获取连接
SqlSession sqlSession = MyBatisUtils.getSession();
//获取mapper代理
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查询条件
//数组类型
//int[] ids = {1,10,12,16};
//list集合类型
//ArrayList<Integer> list = new ArrayList<>();
//list.add(1);
//list.add(10);
//list.add(12);
//list.add(16);
//set集合类型
HashSet<Integer> set = new HashSet<>();
set.add(1);
set.add(10);
set.add(12);
//调用接口方法
//List<User> users = mapper.dynamicSQLForeach(ids);
//List<User> users = mapper.dynamicSQLForeach(list);
List<User> users = mapper.dynamicSQLForeach(set);
//List<User> users = mapper.dynamicSQLForeach(null);
//遍历查询结果
for (User u : users) {
System.out.println(u);
}
//关闭会话
MyBatisUtils.closeSession();
}
7.2.5. set (了解)
set用于update的动态SQL中。
- 使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。
- 使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。
-
遇到的问题
用户更新信息的时候,可能只填入部分信息,因此编写update语句时需要判断字段是否更新,按照之前只写if判断,动态拼接SQL过程如下:
-
UserMapper.xml
<!--动态sql - update只用if判断的写法-->
<update id="dynamicUpdateUser" >
UPDATE
`user`
SET
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="phone != null and phone != ''">
phone=#{phone},
</if>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
WHERE uid = #{uid}
</update>
- 测试
/**
* 测试动态SQL - UPDATE
*/
@Test
public void dynamicSQLUpdate(){
//获取会话连接
SqlSession sqlSession = MyBatisUtils.getSession();
//模拟用户输入的参数
User user = new User();
user.setUid(12);
user.setUsername("蹦迪小王子");
user.setPhone("13266666666");
//模拟不更新性别
// user.setSex("1");
//完成更新
sqlSession.update("test.dynamicUpdateUser",user);
//关闭事务并提交
MyBatisUtils.commitAndclose();
}
- 发现问题
- 当在 update 语句中使用if标签时,如果后面的if没有执行,导致动态拼接的SQL逗号多余错误。
- 当任何参数都没输入时,动态拼接的SQL多余一个set关键字
当不传入sex参数时,对应的SQL:
UPDATE `user` SET username = ?,phone = ?, where uid = ?
当任何参数都没输入时,对应的SQL:
UPDATE `user` where uid = ?
- 解决方案:
- 使用 元素
- 修改UserMapper.xml
<!--动态sql - update只用if判断的写法-->
<update id="dynamicUpdateUser" >
UPDATE
`user`
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="phone != null and phone != ''">
phone=#{phone},
</if>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
</set>
WHERE uid = #{uid}
</update>
- 解决了最后一个条件不输入时多余逗号的问题,但是没有解决所有参数都不输入时的SQL语法问题,需要自己在代码中判断,后续使用mybatis的高级功能(通用mapper)这些问题都已经处理了。
7.2.6. trim(了解)
trim 是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。
trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;
也可以把包含内容的首部或尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;
因此也可以非常简单的利用 trim 来代替 where 元素的功能。
-
trim 代替 where
示例:使用动态SQL多条件查询:根据输入的用户名/手机号/地址进行查询
使用where的写法
- UserMapper.xml
<select id="dynamicSQLForWhere" resultType="user">
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
<where>
<if test="username != null and username != ''">
username LIKE #{username}
</if>
<if test="phone != null and phone != ''">
AND phone = #{phone}
</if>
<if test="address != null and address != ''">
AND address LIKE #{address}
</if>
</where>
</select>
- 测试
@Test
public void dynamicSQLForWhere(){
//获取会话连接
SqlSession sqlSession = MyBatisUtils.getSession();
//模拟用户输入的参数
User user = new User();
user.setUsername("%蹦迪%");
user.setPhone("13266666666");
user.setAddress("%江苏%");
//完成更新
sqlSession.selectList("test.dynamicSQLForWhere",user);
//关闭事务并提交
MyBatisUtils.commitAndclose();
}
使用trim替代where的写法
<select id="dynamicSQLForTrim" resultType="user">
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
<trim prefix="WHERE" prefixOverrides="AND | OR">
<if test="username != null and username != ''">
username LIKE #{username}
</if>
<if test="phone != null and phone != ''">
AND phone = #{phone}
</if>
<if test="address != null and address != ''">
AND address LIKE #{address}
</if>
</trim>
</select>
-
trim 替代set
如果set后面出现逗号,则将其覆盖- 动态更新使用的写法
<!--动态sql - update只用if判断的写法-->
<update id="dynamicUpdateUser" >
UPDATE
`user`
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="phone != null and phone != ''">
phone=#{phone},
</if>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
</set>
WHERE uid = #{uid}
</update>
- 使用<trim>的写法
```xml
<update id="dynamicUpdateForTrim" >
UPDATE
`user`
<trim prefix="set" suffixOverrides=",">
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="phone != null and phone != ''">
phone=#{phone},
</if>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
</trim>
WHERE uid = #{uid}
</update>
- 练习
需求:查询女生中生日在1996-10-01之前的用户,按照uid降序返回
- 手写SQL ,保证SQL 没问题
- 改写SQL片段(statement)
- 编写mapper接口对应的方法
- 测试
- UserMapper.xml
<!--多条件查询+排序-->
<select id="multiplySelect" resultType="user">
SELECT <include refid="baseColumn"/>
FROM `user`
<where>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
<if test="birthday != null">
AND birthday <![CDATA[ < ]]> #{birthday}
</if>
</where>
ORDER BY ${columnName} DESC
</select>
- UserMapper.java
//练习:多条件查询
public List<User> multiplySelect(Map<String,Object> map);
- 测试
/**
* 多条件查询
*/
@Test
public void multiplySelect() throws ParseException {
//使用工具类获取sqlSession
SqlSession sqlSession = MyBatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//模拟接收到的前台参数
HashMap<String, Object> map = new HashMap<>();
map.put("sex","2");
map.put("birthday","1996-10-01");
map.put("columnName","uid");
//根据条件查询
List<User> users = mapper.multiplySelect(map);
for (User u : users) {
System.out.println(u);
}
//关闭会话
MyBatisUtils.closeSession();
}
7.3. CDATA
7.3.1. 特殊符号的问题
假如您在 XML 文档中放置了一个类似 “<” 字符,那么这个文档会产生一个错误,这是因为解析器会把它解释为新元素的开始。因此你不能这样写:
AND birthday < #{birthday}
为了避免此类错误,需要把字符 “<” 替换为实体引用,就像这样:
AND birthday < #{birthday}
在 XML 中有 5 个预定义的实体引用:
实体类 | 符号 | 意义 |
---|---|---|
< | < | 小于 |
> | > | 大于 |
& | & | 和号 |
' | ’ | 省略号 |
" | " | 引号 |
在 XML 中仅有字符 “<“和”&” 是非法的。省略号、引号和大于号是合法的
7.3.2. CDATA
CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed CHARACTER DATA)。
在 XML 元素中,"<" 和 “&” 是非法的。
“<” 会产生错误,因为解析器会把该字符解释为新元素的开始。
“&” 也会产生错误,因为解析器会把该字符解释为字符实体的开始。
- CDATA 部分中的所有内容都会被解析器忽略。
- CDATA 用法 <![CDATA[ 特殊符号 ]]>
- 等这些标签都不要放入<![CDATA[ ]]>中,否则也不会解析,们只把有特殊字符的语句放进去即可, 尽量缩小 <![CDATA[ ]]> 的范围
AND birthday <![CDATA[ < ]]> #{birthday}