MyBatis_3
上一篇文章,我们学习了使用XML实现MyBatis进行增、删、查、改等操作,本篇文章,我们将学习#{ }和${ }获取方法参数的区别和使用MyBatisXML实现动态SQL语句。
#{ }和${ }的区别
在之前的文章中我们都是使用#{ }进行赋值,但实际上Mybatis也支持${ } 对参数进行赋值。
Integer类型的参数
我们先来看Integer类型的参数的赋值:
mapper:
//Integer@Select("select * from userinfo where id = #{id} ")List<Userinfo> getById(Integer id);@Select("select * from userinfo where id = ${id} ")List<Userinfo> getById2(Integer id);
测试:
@Testvoid getById() {System.out.println(mapper.getById(1));}@Testvoid getById2() {System.out.println(mapper.getById(1));}
#{ }:
${ }:
可以看到查询到的结果是一样的,都能够正常查到。此时,我们再换一个String类型试试。
String类型的参数
mapper:
//String@Select("select * from userinfo where username = #{username} ")List<Userinfo> getByName(String username);@Select("select * from userinfo where username = ${username} ")List<Userinfo> getByName2(String username);
测试:
@Testvoid getByName() {System.out.println(mapper.getByName("xmy"));}@Testvoid getByName2() {System.out.println(mapper.getByName2("xmy"));}
#{ }:
测试通过。
${ }:
可以看到报出BadSql异常。
此时观察日志,我们传过去的sql语句是这样的:通过观察,我们发现xmy处少了引号,正确的sql语句是‘xmy’。我们手动给sql语句加上引号,修改代码如下:
@Select("select * from userinfo where username = '${username}' ")List<Userinfo> getByName2(String username);
测试通过。
通过对比#{ }和${ }打出来的日志我们发现#{ }是预编译sql(通过?占位的方式,提前对sql进行编译然后把参数填充到SQL语句中),#{ }会根据参数类型自动拼接引号;而${ }则是直接进行字符替换,一起对SQL进行编译(即时sql)。如果参数为字符串,需要加上引号。
在开发环境下,我们能使用#{ }就不要使用${ }。不仅仅因为#{ }的效率比${ }更高,而是因为${ }可能会产生sql注入的问题。
那么什么是sql注入呢?下面我们通过代码来演示sql注入。
${ }引发sql注入问题
sql注入:通过操作输入的数据来修改事先定义好的sql语句,以达到执行代码对服务器进行攻击的方法。
我们先尝试在数据库中使用下面的sql语句进行查询:
SELECT * FROM `userinfo` where username = ' OR 1 = '1;
可以看到此时我们的代码是有问题的:
下面我们使用#{ }和${ } 分别进行查询:
//String@Select("select * from userinfo where username = #{username} ")List<Userinfo> getByName(String username);@Select("select * from userinfo where username = '${username}' ")List<Userinfo> getByName2(String username);
测试:
@Testvoid getByName() {System.out.println(mapper.getByName("' OR 1 = '1"));}@Testvoid getByName2() {System.out.println(mapper.getByName2("' OR 1 = '1"));}
#{ }:
${ }: 可以看到,${ }依然正常查询出来了,其中参数or被当作了SQL语句的一部分:
${ }的作用
从上面的例子中,我们可以知道:${}会有sql注入的风险,所以我们尽量使用#{}完成查询。
既然如此,${ }是不是就没有存在的必要了呢?
当然不是。接下来我们通过代码来看看${ }的作用。
1、使用${}实现排序功能
mapper:
@Select("select * from userinfo order by id ${order}")List<Userinfo> getByOrder(String order);
测试:
@Testvoid getByOrder() {System.out.println(mapper.getByOrder("desc"));}
可以看到,能够根据id逆序输出结果。此时我们将${ }改成#{ } 。
@Select("select * from userinfo order by id #{order}")List<Userinfo> getByOrder(String order);
测试结果:
可以发现,当使用#{sort}查询时,desc前后加上了引号,导致sql错误。
2、使用${ }实现模糊查询
mapper:
@Select("select * from userinfo where username like '%${username}%'")List<Userinfo> getByLike(String username);
测试:
@Testvoid getByLike() {System.out.println(mapper.getByLike("zhangsan"));}
此时我们将${ } 改为#{ } :
@Select("select * from userinfo where username like '%#{username}%'")List<Userinfo> getByLike(String username);
可以看到,依然是因为引号的关系,出现了异常。
但是在模糊查询中使用#{ }也有解决办法 :我们可以使用concat()来拼接字符串:
@Select("select * from userinfo where username like concat('%',#{username},'%')")List<Userinfo> getByLike1(String username);
测试:
@Testvoid getByLike1() {System.out.println(mapper.getByLike1("zhangsan"));}
总结: #{ }和${ }的区别
1、#{ }和${ }的区别就是预编译sql(占位)和即时sql(直接拼接)的区别。
2、#{ }使用预编译的形式所以性能会比${ }更高
绝大多数情况下,某一条sql语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如select的where子句值不同,insert的values值不同)。如果每次都需要上面语法解析,sql优化,sql编译等过程,效率就明显不行了。
预编译sql,编译一次之后会将编译后的sql语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率。
3、#{ }更安全(防止sql注入)
在使用${ }的场景下一定一定要考虑到sql注入问题,并采取措施进行防止:例如:1、在接口层(Controller层)进行判定,如果输入的结果不是我们想要的直接返回。2、直接给接口写死,根据用户输入的内容来决定调用哪个接口,如果没有接口符合,则返回。
4、在一些场景下仍然需要用到${ }
比如:排序,参数不需要引号……
数据库连接池
在MyBtis中,我们使用了数据库连接池技术,避免频繁地创建销毁连接。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有地数据库连接,而不是重新建立一个。
没有使用数据库连接池的情况:每次执行sql语句,要先创建一个新的连接对象,然后执行sql语句,sql语句执行完,再关闭连接对象释放资源。这种重复的创建连接,销毁连接比较消耗资源。
使用数据库连接池的情况:程序启动时,会在数据库连接池中创建一定数量的Connection对象,当客户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行sql,sql语句执行完,再把Connection归还给连接池。
优点:
1、减少了网络开销
2、资源重用
3、提升了系统性能
动态SQL
动态sql是mybaatis的强大特性之一,能够完成不同条件下不同的sql拼接。
官方文档:动态 SQL_MyBatis中文网
<if>标签
在注册用户的时候,可能会有这样一个问题,如下图所示:
注册分为两种字段:必填字段和非必填字段,那如果在添加用户的时候有不确定的字段传入,程序应该如何实现呢?
这时候就需要使用动态标签进行判断了,如添加的时候性别为非必填字段,具体实现如下(XML实现):
Mapper:
Integer insertBatch(Userinfo userinfo);
XML:
<insert id="insertBatch">insert into userinfo (username,password,age<if test="gender!=null">,gender</if>)values (#{username},#{password},#{age}<if test="gender!=null">,#{gender}</if>)</insert>
测试(有性别):
@Testvoid insertBatch() {Userinfo userinfo = new Userinfo();userinfo.setUsername("lisi");userinfo.setAge(16);userinfo.setPassword("lisi666");userinfo.setGender(2);mapper.insertBatch(userinfo);}
测试(无性别):
@Testvoid insertBatch() {Userinfo userinfo = new Userinfo();userinfo.setUsername("lisi");userinfo.setAge(16);userinfo.setPassword("lisi666");mapper.insertBatch(userinfo);}

if标签详解:
<trim>标签
mapper:
Integer insertBatch2(Userinfo userinfo);
假如我们有许多的元素需要选填,那么我们此时的XML语句会变成这样:
<insert id="insertBatch2">insert into userinfo(<if test="username != null">username</if><if test="password != null">,password</if><if test="age != null">,age</if><if test="gender != null">,gender </if>)values (<if test="username != null">#{username}</if><if test="password != null">,#{password}</if><if test="age != null">,#{age}</if><if test="gender != null">,#{gender}</if>)</insert>
我们测试的时候选填其中两个参数(性别,年龄):
@Testvoid insertBatch2() {Userinfo userinfo = new Userinfo();userinfo.setGender(1);userinfo.setAge(16);mapper.insertBatch2(userinfo);}
测试不通过,观察报错日志发现是因为<if>标签的原因,导致我们sql语句多加了个逗号。
那么我们能不能把逗号加在后面呢?同样的,如果将逗号加在后面,那么后面也会多一个逗号。
下面我们使用<trim>标签来解决问题:
<insert id="insertBatch2">insert into userinfo<trim prefix="(" suffix=")" prefixOverrides=","><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=")" prefixOverrides=","> <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>
测试通过:
那么<trim>标签的作用是什么呢?
<trim>标签详解:
<where>标签
我们在淘宝上逛东西时,通常会有一些按钮能够动态组装我们的查询条件。
这种功能如何实现呢?
1、通过上面的<trim>标签和<if>标签实现
mapper:
List<Userinfo> queryByConditin(Userinfo userinfo);
XML:
<select id="queryByConditin" resultType="com.example.mybatis.model.Userinfo">select * from userinfo<trim prefix="where" prefixOverrides="and"><if test="username != null">username = #{username}</if><if test="age != null">and age = #{age}</if><if test="gender != null">and gender = #{gender}</if><if test="password != null">and password = #{password}</if></trim></select>
测试(查询姓名为:“lisi” 年龄为:16的用户):
@Testvoid queryByConditin() {Userinfo userinfo = new Userinfo();userinfo.setUsername("lisi");userinfo.setAge(16);System.out.println(mapper.queryByConditin(userinfo));
这种查询方法固然能够成功,但是并不专业而且如果我们不添加任何查询条件时sql语句会多出来一个where导致Badsql异常,我们可以使用where标签来代替<trim>标签。
2、使用<where>标签实现
XML代码:
<select id="queryByConditin" resultType="com.example.mybatis.model.Userinfo">select * from userinfo<where><if test="username != null">username = #{username}</if><if test="age != null">and age = #{age}</if><if test="gender != null">and gender = #{gender}</if><if test="password != null">and password = #{password}</if></where></select>
测试:
测试不加任何条件:
@Testvoid queryByConditin() {Userinfo userinfo = new Userinfo();System.out.println(mapper.queryByConditin(userinfo));}
可以发现我们传入的sql语句并没有where。
<where>标签详解:
<set>标签
与选择查询相同有时候我们也要更新一些用户的选项值,而保证其他值不变,比如:用户修改手机号、用户修改密码等。
同样的,这一功能也能通过<trim>标签和<if>标签实现:
mapper:
Integer updateByConditin(Userinfo userinfo);
XML:
<update id="updateByConditin">update userinfo<trim prefix="set" suffixOverrides=","><if test="username != null">username = #{username},</if><if test="age != null">age = #{age},</if><if test="gender != null">gender = #{gender},</if><if test="password != null">password = #{password},</if></trim>where id = #{id}</update>
测试(修改id为6的用户名和密码):
@Testvoid updateByConditin() {Userinfo userinfo = new Userinfo();userinfo.setPassword("123456");userinfo.setUsername("wangwu");userinfo.setId(6);mapper.updateByConditin(userinfo);}
同样的,我们也可以使用<set>标签来代替这里的<trim>标签和<if>标签:
<update id="updateByConditin">update userinfo<set><if test="username != null">username = #{username},</if><if test="age != null">age = #{age},</if><if test="gender != null">gender = #{gender},</if><if test="password != null">password = #{password},</if></set>where id = #{id}</update>
测试(修改id为7的用户名和密码):
@Testvoid updateByConditin() {Userinfo userinfo = new Userinfo();userinfo.setPassword("666666");userinfo.setUsername("wuwuwu");userinfo.setId(7);mapper.updateByConditin(userinfo);}