> 技术文档 > MyBatis_3

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); }

冰淇淋机