MyBatis——#{} 和 ${} 的区别和动态 SQL
1. #{} 和 ${} 的区别为了方便,接下来使用注解方式来演示:
https://img-blog.csdnimg.cn/img_convert/642bc3931bd4b4021e13c9f02b2eb7b2.png
#{} 的 SQL 语句中的参数是用过 ? 来起到类似于占位符的作用,而 ${} 是直接进行参数更换,这种直接更换的即时 SQL 就大概会出现一个标题
https://img-blog.csdnimg.cn/img_convert/0ee7a2c87d207cc945f3cd16c35a6aed.png
当传入一个字符串时,就会发现 SQL 语句出错了:
https://img-blog.csdnimg.cn/img_convert/43bc1d65740cf5686533637680a7fc4f.png
https://img-blog.csdnimg.cn/img_convert/b49c9395cc2efdfbba1213d9ff1ff0a3.png
这里的 zhangsan并不是作为一个字符串使用的,应该是加上引号的
https://img-blog.csdnimg.cn/img_convert/d76ee393066c8b9939ceb5c4d5a10ed1.png
加上之后就可以正常查询了
这就大概会出现 SQL 注入的标题
来看一下 SQL 注入的例子,假如传入的参数是' or 1='1
@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")
List<UserInfo> queryByName(String name); 按原理说是没有这个用户的,但是却把全部用户的信息都查出来了
https://img-blog.csdnimg.cn/img_convert/1bfad853750b0c2b09b47d4841f6b0f4.png
如果在某些登录的界面输入 SQL 注入代码' or 1='1就大概登录成功
使用 #{} 就没有这个标题
除了以上的区别外,二者另有性能方面的区别
在上面提到过,#{} 是预编译 SQL,${} 是即时 SQL ,预编译SQL编译一次之后会将编译后的 SQL 语句缓存起来,背面再执行这条语句时,不会再次编译,省去了解析优化等过程,以此来进步效率,以是当需要频仍地使用 SQL 语句时,预编译的性能优化就表现出来了,而对于即时 SQL ,如果只是在启动时大概很少变化的场景下使用${}来配置一些数据库对象名称等,它可以避免预编译的过程,执行起来相对直接
https://img-blog.csdnimg.cn/img_convert/f2bed393304b1618f7282130354444c3.png
2. 排序
在上面看来,#{} 无论是在安全性照旧效率上,都占据了上风,那么都是用 #{}可以吗?
来看使用 #{}来实现排序功能:
@Select("select * from user_info order by id #{order}")
List<UserInfo> selectUserByOrder(String order); 这里把排序的方式作为参数,给用户选择是升序照旧降序排序,测试方法中传入一个字符串表示降序
@Test
void selectUserByOrder() {
userInfoMapper.selectUserByOrder("desc");
}
https://img-blog.csdnimg.cn/img_convert/9c9202ee6c31627c73cbd2bb812546ca.png
然后就会发现报错了,可以看到 "desc" 确实是当做字符串传进去了,#{} 的方式会把字符串类型加上单引号,然后 SQL 语句就会酿成这样:
select * from user_info order by id 'order'
这样肯定是不对的,那么这个时间就需要用到 ${} 了,直接进行参数更换,但是使用 ${} 肯定就需要思量 SQL 注入的标题,由于排序方式只有 asc 和 desc 两种方式,可以采用枚举类来进行校验,也可以通过判断条件来实现校验
https://img-blog.csdnimg.cn/img_convert/c8d94e0e62d13c945e16641a3020e151.png
3. 模糊查询
通过模糊查询来查找名字中含有“zhang”的信息
@Select("select * from user_info where username like '%#{name}%'")
List<UserInfo> selectUserByLike(String name); @Test
void selectUserByLike() {
System.out.println(userInfoMapper.selectUserByLike("zhang"));
}
https://img-blog.csdnimg.cn/img_convert/d71d1f23220fb2285362a4ec805211a6.png
然后发现又报错了,因为使用的是 #{} ,以是就会更换为 '%'zhang'%',这样是肯定不能运行的,以是照旧需要使用 ${} 进行直接更换,但是这时怎么去办理 SQL 注入的标题呢,这样就不能简朴的通过枚举大概判断来约束传入的参数了,这时就可以通过使用拼接的方式
通过 CONCAT 函数来对 SQL 语句进行拼接,这样就可以使用 #{},
@Select("select * from user_info where username like CONCAT('%',#{name},'%')")
List<UserInfo> selectUserByLike(String name); 4. 数据库连接池
在传统的数据库访问模式中,每当应用程序需要与数据库进行交互时,它会创建一个新的数据库连接,使用完毕后关闭连接,这样频仍地创建和销毁数据库连接会斲丧大量的体系资源
数据库连接池的出现就是为了办理这些标题。它在应用程序启动时预先创建一定命量的数据库连接,将这些连接存储在一个 “池” 中。当应用程序需要访问数据库时,从池中获取一个可用的连接,使用完毕后将连接归还给池,而不是直接关闭连接,从而避免了频仍创建和销毁连接所带来的性能开销,这一点和线程池是类似的
https://img-blog.csdnimg.cn/img_convert/e89890483872b484d53791a6bfcc2392.png
常见的数据库连接池有:C3P0 , DBCP , Druid , Hikari
Spring Boot 默认使用的是 Hikari
https://img-blog.csdnimg.cn/img_convert/f7d411c40ce9dbb6949c1a4464b1f829.png
如果想更换为 Druid 的话,导入相干的依赖即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency> 然后再启动程序之后就更换为了 Druid
https://img-blog.csdnimg.cn/img_convert/9d4141f83769d5a6d3d05ea793f17ebf.png
也可以去 官方文档 进行查看
5. 动态 SQL
我们在填一些表单的时间应该访问到下面这种,有的是必填项,有的是选填项,对于选填项来说,如果没有填,肯定是需要赋一个默认值的,比如 null,那么就需要动态 SQL 来实现这样的功能
https://img-blog.csdnimg.cn/img_convert/23361b885bc34cdb5d58ad7ad53dad71.png
5.1. <if>
可以通过 if 标签来实现一下:
@Mapper
public interface UserInfoXmlMapper {
Integer insertUserByCondition(UserInfo userInfo);
} 再来看 XML 中的 SQL 语句
<insert id="insertUserByCondition">
insert into user_info(username,'password',age,
<if test="gender != null">
gender
</if>
)
values (#{username},#{password},#{age},
<if test="gender != null">
#{gender}
</if>
)
</insert> if 标签中的参数和 java 对象中的属性参数是对应的
https://img-blog.csdnimg.cn/img_convert/7f3a9311f1960bc160df8d92125fa803.png
@Test
void insertUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("java");
userInfo.setPassword("java");
userInfo.setAge(19);
//userInfo.setGender(1);
Integer integer = userInfoXmlMapper.insertUserByCondition(userInfo);
} 如果不传入性别的话来看一下结果:
https://img-blog.csdnimg.cn/img_convert/1554fc6d1068dabdd86965cae296b116.png
由于性别没有传入,以是说 SQL 语句中是只有前三个参数的,以是第三个参数那里就多了一个逗号,导致终极的 SQL 的语法错误
那么就可以想一个办法,如果把逗号直接加前面,是不是就可以办理了
https://img-blog.csdnimg.cn/img_convert/4a2507e881c5cf9ba334fe1a42c63f8e.png
这样看似是可以办理的,但是如果说 username, age 都设为了非必填的,例如 username 没有传入参数,但是 age 传入了参数,这样前面就多了一个逗号,这时 SQL 语句就又会出错了,把逗号都加到右边,也是会出现标题标
这时就需要用到下面的标签了
5.2. <trim>
主要用于去除 SQL 语句中多余的关键字大概字符,同时也可以添加自界说的前缀和后缀
・prefix:用于为包罗在trim标签内部的 SQL 语句块添加一个前缀
・suffix:表示整个语句块,以 suffix 的值作为后缀.
・prefixOverrides:为trim标签内的 SQL 语句块添加一个后缀.
・suffixOverrides:表示整个语句块要去除掉的后缀.
<insert id="insertUserByCondition">
insert into user_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
username ,
</if>
<if test="password!=null">
`password`,
</if>
<if test="age!=null">
age,
</if>
<if test="gender!=null">
gender
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
#{username},
</if>
<if test="password!=null">
#{password},
</if>
<if test="age!=null">
#{age},
</if>
<if test="gender!=null">
#{gender}
</if>
</trim>
</insert> 这就表示在 SQL 语句前面加上一个 '(' ,背面加上 ')' ,如果最后是以逗号结尾的就把逗号删了,以此来实现 SQL 语句拼接的结果
5.3. <where>
来看一下条件查询
https://img-blog.csdnimg.cn/img_convert/3a5a0ba2b2b08050ef500a47fd02eb6e.png
这里的 and 和上面的逗号是一样的性子,放在右边大概左边都不符合,照旧可以使用 trim 标签来办理
https://img-blog.csdnimg.cn/img_convert/62ae65dc1ed8328d75a5d2553d02ccdb.png
但是这时着实另有一个标题,如果说 age 和 deleteFlag 都没有传入的话,最后的 SQL 语句 where 背面就没有了,这时又会报错了
https://img-blog.csdnimg.cn/img_convert/3fe782ed7cd866f0fd914b345a82bf72.png
这种环境 trim 就办理不了了,其中一种办理方式是在 where 背面加上 1=1,那么 and 就需要加在前面了:
https://img-blog.csdnimg.cn/img_convert/af774702886403e2b11ba57b5183566f.png
比较保举的写法就是使用 <where> 标签
<select id="selectUserByCondition" resultType="com.example.mybatisdemo.model.UserInfo">
select * from user_info
<where>
<if test="age!=null">
and age=#{age}
</if>
<if test="deleteFlag!=null">
and delete_flag = #{deleteFlag}
</if>
</where>
</select> <where> 标签如果背面都没有值的话,SQL 语句中的 where 也不会添加,并且如果只有一个值的话,前面的 and 也会被去掉,也不用 trim 标签了,不外去掉的是前面的 and,写背面是不会去掉的
5.4. <set>
动态更新操作也是,当背面有值的时间就更新,没有值的时间就不更新,<set> 标签的作用和 where 类似,也是背面有值的话就生成 set 关键字并且去除右边的逗号,但是背面设置的内容也不能全部是空,此时就算没有生成 set 标签,但是前面另有一个 update 关键字,最后的 SQL 语句照旧有标题
<update id="updateByCondition">
update user_info
<set>
<if test="username!=null">
username = #{username},
</if>
<if test="password!=null">
password = #{password},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</set>
<where>
id = #{id}
</where>
</update> 5.5. <foreach>
foreach 用于在 SQL 语句中遍历聚集,动态地构建包罗多个参数的 SQL 语句,比如IN子句、批量插入语句等
[*]collection:绑定方法参数中的聚集,如 List,Set,Map 或数组对象。
[*]item:遍历时的每一个对象。
[*]open:语句块开头的字符串。
[*]close:语句块竣事的字符串。
[*]separator:每次遍历之间隔断的字符串。
<delete id="batchDelete">
delete from user_info where id in
<foreach collection="ids" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
https://img-blog.csdnimg.cn/img_convert/7c93d23b41adba2ac525caafb7187d59.png
5.6. <include>
<include>标签主要用于代码复用。它可以将一个 SQL 片段(通常是在<sql>标签中界说的)包罗到另一个 SQL 语句中,使得 SQL 语句的编写更加模块化,减少重复代码
https://img-blog.csdnimg.cn/img_convert/b6826103d78330322a2e52efb3e967f1.png
例如上面的重复语句就可以提取出来
<sql id="insertCol">
insert into user_info(username, password, age, gender)
</sql> 然后就可以通过 include 标签来引用了
https://img-blog.csdnimg.cn/img_convert/64d04a120d30b1fd2011226f1228e699.png
6. 注解方式的动态 SQL
注解方式就是把原来 XML 中的 SQL 语句部分写到注解的 <script> 标签下,可以看出,由于注解中是字符串拼接的方式,这种方法是非常轻易出错的,而且排查错误也是有些困难的
https://img-blog.csdnimg.cn/img_convert/11e31d4ba519c88fc110e189875ed271.png
主页
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]