MyBatis(三) 动态SQL标签
上一章已经实现了最简单的CRUD的操作,不过还是有一些细节问题需要进一步的学习
一.#{}
与${}
在前面的章节中,大家已经看过很多次#()这个符号了,虽然没有专门的去讲解,但是也应该能猜出#{}
应该就是一个参数的占位符,是的,大家猜测的没错,不过这个占位符除了#{}
之外,还有一个${}
,两者都是起到占位的作用,但又是完全不同的.
简单来说, #{}
其实就相当于我们之前在JDBC代码中使用了PreparedStatement,并在SQL语句中使用了 ?
替代符
而 ${}
的占位,就相当于SQL语句直接使用了字符串拼接,而且还需要自己加上``哦.
上面的意思我们用伪代码形容一下
select * from t_user where username=#{username}
就相当于
select * from t_user where username=?
而且会在随后的代码执行中自动用你传入的 `username` 参数将`?`替代掉,比如`username`的值是`张三`
select * from t_user where username='张三'
select * from t_user where username=${username}
比如 参数 username 的值是张三,那就相当于
select * from t_user where username=张三
注意这里是错误的哦,因为张三是个字符串,你需要自己加上单引号
select * from t_user where username='${username}'才是正确的写法
所以,总结来说:
#{}:解析为一个JDBC预编译语句(PreparedStatement)的参数标记,一个#()
会被解析为一个参数占位符?
,在数据库中发生参数的替换
${}: 仅仅为一个纯粹的String字符换的替换,而且在动态SQL的解析阶段就讲变量进行替换,而且如果参数就是一个简单类型的话(意思是不是一个自定义对象,就是一个int,或者String),那么参数的占位符只能使用value,也就是上面的 username="张三",如果就只有这么一个值,使用 ${}
替代符就只能使用value占位,也就是要写为 select * from t_user where username=${value}
因此一般情况下,我们当然优先使用#()
,还能一定程度上防止SQL注入,但是有些时候使用${}
却很方便,比如在模糊查询的时候
...
<!-- 根据名称模糊查询用户信息 -->
<select id="getUserByName" parameterType="string" resultMap="userMap">
select * from t_user where username like '%${value}%'
</select>
...
下面的截图,展示了上面文字描述的一些问题
二.动态SQL
上面的例子只是根据用户的名字进行了模糊查询,这个时候问题就来了,如果要使用多条件查询呢?那就需要用到MyBatis提供的动态SQL功能了,通过 if, choose, when, otherwise, trim, where, set, foreach
标签,可组合成非常灵活的SQL语句
1. where
与if
还是直接通过应用场景来解释,相信大家之前都写过多条件查询的SQL语句了,基本的思考点就是到底有哪些条件?哪些条件不为空的时候才进行SQL拼接?多条件后面的and或者or该怎么搞定?这个问题,在大家的学习过程中,最早期的代码应该是这么写的
......
String sql = "select * from t_user where 1=1 "
if(username != null && !"".equals(username)){
sql += " and username like '%" + username + "%'";
}
if(userTel != null && !"".equals(userTel)){
sql += " and userTel ='" + userTel + "'";
}
...
在早期的JDBC代码中,为了避免SQL语句后面拼接and
的问题,一般都会人为的在前面加上一个条件1=1
,使用MyBatis其实也跳不过这个问题,不过我们使用MyBatis自带的标签来解决这个问题
<!--多条件查询动态SQL-->
<select id="getUserBySelective" parameterType="user" resultMap="userMap">
select * from t_user
<where>
<if test="id >= 1">
id=#{id}
</if>
<if test="userTel != null">
and user_tel like '%${userTel}%'
</if>
<if test="username != null">
and username like '%${username}%'
</if>
<if test="registrationTime != null">
and Date_Format(registration_time,'%Y-%m-%d')=#{registrationTime}
</if>
</where>
</select>
上面的语句就用到了MyBatis的where和if标签,具体标签的含义其实不用多做解释大家也能看懂,而且最好的地方是完美的解决了之前and拼接的问题,下面的截图给大家展示了这些效果
2. trim
与if
where标签也完全可以用trim标签替代,一般情况下的写法是这个样子的
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
其实关键就是prefix
和prefixOverrides
这两个属性
prefix
: 前缀,这里的意思就是SQL语句加上where
prefixOverrides
: 去掉第一个and或者是or,就是后面的拼接语句中出现了and或者or,如果出现在第一个条件中就去掉
上面的代码,我们通过trim
标签替换
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="id >= 1">
id=#{id}
</if>
<if test="userTel != null">
and user_tel like '%${userTel}%'
</if>
<if test="username != null">
and username like '%${username}%'
</if>
<if test="registrationTime != null">
and Date_Format(registration_time,'%Y-%m-%d')=#{registrationTime}
</if>
</trim>
3. set
与if
既然查询是多条件的,我们回过头看看之前写过的新增insert
和修改update
方法,按照之前的写法,如果有条件不传,会出现什么问题,我们看一下之前的修改方法
这样肯定和实际情况不符,我们新加一个修改方法,根据传入的值进行判断修改,这样改的关键点其实还是判断要修改的值是否为空,不为空再进行修改,问题的关键点还是多个条件判断的时候,最后一个逗号(,)截取的问题,在MyBatis中,使用set
标签
同样,<trim>
标签也可以完全替换<set>
<update id="updateUserByIdSelective" parameterType="user">
update t_user
<trim prefix="set" suffixOverrides=",">
<if test="userTel != null">
user_tel=#{userTel},
</if>
<if test="username != null">
username=#{username},
</if>
<if test="password != null">
password=md5(#{password}),
</if>
<if test="registrationTime != null">
registration_time=#{registrationTime}
</if>
</trim>
where id=#{id}
</update>
suffixOverrides
: 去掉最后一个逗号(,)后缀
在新增语句中,也是一样,如果只是选择性的要插入一些字段(当然前提是数据库中该字段可以为null),看一下之前新增数据的效果
如果要选择性的拼接SQL,新增的SQL语句拼接会涉及到表字段,字段对应的值以及左括号,逗号,右括号,这样拼接判断的时候复杂度稍微高一些,所以,这里使用<trim>
标签是最好的选择
<!--根据传入的参数新增用户信息-->
<insert id="insertUserSelective" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into t_user
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="userTel != null">
user_tel,
</if>
<if test="username != null" >
username,
</if>
<if test="password != null" >
password,
</if>
<if test="registrationTime != null" >
registration_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=INTEGER},
</if>
<if test="userTel != null" >
#{userTel,jdbcType=VARCHAR},
</if>
<if test="username != null" >
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null" >
md5(#{password,jdbcType=VARCHAR}),
</if>
<if test="registrationTime != null" >
#{registrationTime,jdbcType=VARCHAR},
</if>
</trim>
</insert>
4. SQL
和include
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
比如上面新增语句的字段条件判断,插入语句实在写的太多,可以将这部分的代码提出来,直接写成代码片段,到时候再用<include>
标签引用就可以了
这种用法特别是在写查询语句的时候,字段名较多的情况下,把经常要现实出来的字段用SQL标签引入,在写SQL语句的时候,再直接通过include引入字段就可以了
5. choose(when,otherwise)
上面的where
和set
标签都是在和if
标签配合,判断标签除了if
之外,还有choose(when,otherwise)
标签,choose(when,otherwise)
其实就类似于java的switch
语句,把之前的查询语句,从where...if...
的组合换成 where...choose(when,otherwise)
,但是要注意两者之间的区别,choose(when,otherwise)
同时只能成立一个条件
<!--多条件查询动态SQL,使用where...choose(when...otherwise)组合-->
<select id="getUserBySelective2" parameterType="user" resultMap="userMap">
select * from t_user
<where>
<choose>
<when test="id >= 1">
id=#{id}
</when>
<when test="userTel != null">
and user_tel like '%${userTel}%'
</when>
<when test="username != null">
and username like '%${username}%'
</when>
<otherwise>
and Date_Format(registration_time,'%Y-%m-%d')=#{registrationTime}
</otherwise>
</choose>
</where>
</select>
6. foreach
先来捋一捋代码场景,比如同时要查询多个值的场景,意思是SQL语句要写成类似于下面的这个样子:
select * from t_user where id=1 or id=4 or id=5 or id=8
或者
select * from t_user where id in(1, 4, 5, 8)
注意: 这个样子的SQL语句,为了传输值方便,不破坏User类,我们再封装一个Bean类,就叫做查询Bean,这个Bean就是为了查询而服务的,什么意思呢?界面上可能会有各种条件的查询,比如注册的开始日期,结束日期,根据这种情况进行查询,但是我们现在的User类其实只是完全和数据库对应的类,User类里只有一个注册日期,没有什么注册开始日期和结束日期的概念,但是在查询的时候却需要查询这些东西,所以干脆就专门建一个实体类,来处理封装界面上要查询的内容,这样就可以方便的将界面上的数据传输给Dao层.这其实是分层架构中VO,PO,DO,DTO等等领域模型的概念,有兴趣的可以自己查询了解一下
无论如果,为了封装方便,这里新创建一个类 QueryBean.java:
import java.util.List;
public class QueryBean {
//封装要查询的多个用户的id
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
在UserMapper.xml中添加根据多个id查询的方法
<select id="getUserByQueryIds" parameterType="queryBean" resultMap="userMap">
select * from t_user
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from t_user where (id=1 or id=4 or id=5 or id=8)
-->
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
注意下图中,foreach标签中每个属性的作用
也可以稍微换一种写法
<select id="getUserByQueryIds" parameterType="queryBean" resultMap="userMap">
select * from t_user
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from t_user where (id=1 or id=4 or id=5 or id=8)
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id=#{id}
</foreach>
-->
<!-- select * from t_user where id in (1, 4, 5, 8) -->
<foreach collection="ids" item="id" open="id in (" close=")" separator=",">
#{id}
</foreach>
</where>
</select>
Comments