玖叶教程网

前端编程开发入门

MyBatis手把手跟我做系列(三) ——动态SQL标签



上一章已经实现了最简单的CRUD的操作(MyBatis手把手跟我做系列(二) --- 基本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:

//封装要查询的多个用户的id
import java.util.List;public class QueryBean { 
 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>




以上就是今天的知识分享啦~

如果大家有问题或者想了解更多的

技术干货可以加朗妹儿微信哟~

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言