MyBatis_3
上一篇文章,我们学习了使用XML实现MyBatis进行增、删、查、改等操作,本篇文章,我们将学习#{ }和${ }获取方法参数的区别和使用MyBatisXML实现动态SQL语句。
#{ }和${ }的区别
在之前的文章中我们都是使用#{ }进行赋值,但实际上Mybatis也支持${ } 对参数进行赋值。
Integer类型的参数
我们先来看Integer类型的参数的赋值:
mapper:
//Integer @Select(\"select * from userinfo where id = #{id} \") List getById(Integer id); @Select(\"select * from userinfo where id = ${id} \") List getById2(Integer id);
测试:
@Test void getById() { System.out.println(mapper.getById(1)); } @Test void getById2() { System.out.println(mapper.getById(1)); }
#{ }:
${ }:
可以看到查询到的结果是一样的,都能够正常查到。此时,我们再换一个String类型试试。
String类型的参数
mapper:
//String @Select(\"select * from userinfo where username = #{username} \") List getByName(String username); @Select(\"select * from userinfo where username = ${username} \") List getByName2(String username);
测试:
@Test void getByName() { System.out.println(mapper.getByName(\"xmy\")); } @Test void getByName2() { System.out.println(mapper.getByName2(\"xmy\")); }
#{ }:
测试通过。
${ }:
可以看到报出BadSql异常。
此时观察日志,我们传过去的sql语句是这样的:通过观察,我们发现xmy处少了引号,正确的sql语句是‘xmy’。我们手动给sql语句加上引号,修改代码如下:
@Select(\"select * from userinfo where username = \'${username}\' \") List 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 getByName(String username); @Select(\"select * from userinfo where username = \'${username}\' \") List getByName2(String username);
测试:
@Test void getByName() { System.out.println(mapper.getByName(\"\' OR 1 = \'1\")); } @Test void getByName2() { System.out.println(mapper.getByName2(\"\' OR 1 = \'1\")); }
#{ }:
${ }: 可以看到,${ }依然正常查询出来了,其中参数or被当作了SQL语句的一部分:
${ }的作用
从上面的例子中,我们可以知道:${}会有sql注入的风险,所以我们尽量使用#{}完成查询。
既然如此,${ }是不是就没有存在的必要了呢?
当然不是。接下来我们通过代码来看看${ }的作用。
1、使用${}实现排序功能
mapper:
@Select(\"select * from userinfo order by id ${order}\") List getByOrder(String order);
测试:
@Test void getByOrder() { System.out.println(mapper.getByOrder(\"desc\")); }
可以看到,能够根据id逆序输出结果。此时我们将${ }改成#{ } 。
@Select(\"select * from userinfo order by id #{order}\") List getByOrder(String order);
测试结果:
可以发现,当使用#{sort}查询时,desc前后加上了引号,导致sql错误。
2、使用${ }实现模糊查询
mapper:
@Select(\"select * from userinfo where username like \'%${username}%\'\") List getByLike(String username);
测试:
@Test void getByLike() { System.out.println(mapper.getByLike(\"zhangsan\")); }
此时我们将${ } 改为#{ } :
@Select(\"select * from userinfo where username like \'%#{username}%\'\") List getByLike(String username);
可以看到,依然是因为引号的关系,出现了异常。
但是在模糊查询中使用#{ }也有解决办法 :我们可以使用concat()来拼接字符串:
@Select(\"select * from userinfo where username like concat(\'%\',#{username},\'%\')\") List getByLike1(String username);
测试:
@Test void 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中文网
标签
在注册用户的时候,可能会有这样一个问题,如下图所示:
注册分为两种字段:必填字段和非必填字段,那如果在添加用户的时候有不确定的字段传入,程序应该如何实现呢?
这时候就需要使用动态标签进行判断了,如添加的时候性别为非必填字段,具体实现如下(XML实现):
Mapper:
Integer insertBatch(Userinfo userinfo);
XML:
insert into userinfo (username,password,age ,gender ) values (#{username},#{password},#{age} ,#{gender} )
测试(有性别):
@Test void insertBatch() { Userinfo userinfo = new Userinfo(); userinfo.setUsername(\"lisi\"); userinfo.setAge(16); userinfo.setPassword(\"lisi666\"); userinfo.setGender(2); mapper.insertBatch(userinfo); }
测试(无性别):
@Test void insertBatch() { Userinfo userinfo = new Userinfo(); userinfo.setUsername(\"lisi\"); userinfo.setAge(16); userinfo.setPassword(\"lisi666\"); mapper.insertBatch(userinfo); }

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