本篇继续介绍Mybatis的相关内容
目录
一、查询不全问题
在上一篇中,我们进行了MyBatis查询操作的测试,但这个查询操作其实是不全的,我们通过查询的日志来观察一下:
可以发现,在查出来的数据中deleteFlag、createTime、updateTime都为null,而在数据库中这些数据又都是存在的。为什么会查不到这些数据呢?原因很简单,MyBaits在将查询结果赋值给对象中的属性时,是以属性名称为依据的,只有与字段名称相符的属性才能拿到值。显然,这三个没有查询到的属性和字段的名称是不一致的。既然如此,直接把名称修改成一样的问题不就解决了吗?
这种做法虽然能够解决问题,但并不推荐这种做法,因为在Java中,属性的名称通常是采取“小驼峰”(第一个单词首字母小写,后面的单词首字母小写)的方式,而字段则是采取的“蛇形”(单词间用_ 连接),我们在这里对属性和字段任意一个改名都会造成这种命名规范被破坏,从而造成一些意想不到的问题。所以不推荐改名。下面,介绍一下三种比较推荐的方式:
起别名
在Mysql中可以对字段进行起别名的操作,因此我们可以在写sql时给字段起一个与属性名称相同的别名,这样问题就解决了。我们具体来看一下:
在编写sql时起别名:
然后测试一下:
可以发现所以字段的值已经成功获取到了。
结果映射
另一种方法是建立字段与属性的映射。xml和注解建立映射的方式有所不同,我们先来看一下在注解中如何建立映射。
在注解中建立映射需要使用@Results注解,具体映射代码如下:
其中id表示 这个映射的名称,value为映射数组,@Result表示具体的映射关系,column表示字段名称,property表示属性名称。
下面我们来测试一下(前面在xml中写的实现已经注释了)
可以发现字段的值同样全获取到了。
这里定义的映射在其他方法里可以直接使用,使用时需要使用@ResultMap,下面我们在上一篇中定义的通过id查询用户的方法上加一下这个注解:
测试一下,通过日志可以发现,前面定义的映射关系使用成功了。
在xml中设置映射的方式略有不同
我们需要先定义一个映射关系,具体如下:
其中<resultMap>代表映射,其中的属性id代表映射名称,type为映射中的属性所在类的全限定名。<result>代表具体的映射关系,其中的column和property和@Result中的含义一样。如果我们需要使用这个映射,只需要在标签中加上一个resultMap属性(值为映射名),具体如下:
通过方法测试一下
可以发现映射生效了。
配置驼峰转换
最后一种方法是配置驼峰转换。前面介绍过,之所以会查询不全,是因为Java这边采用小驼峰的方式来命名属性,而数据库则是采用“蛇形”来命名字段,从而导致名称不一致。使用驼峰转换会自动在这两种命名方式间进行映射,配置驼峰转换需要在配置文件加上这样一条配置:
下面我们来测试一下(结果映射和别名的部分已经删除)
通过日志可以发现,所有字段的值都已经拿到了 。
上述这三种方法,一般推荐使用第三种,使用简单的同时还不容易出错。
#{}和${}
前面我们在sql中获取方法中参数时都是采用的#{},其实还有另一种方式,那就是使用${},下面我们来看看这两种方式的区别。
实现方式
#{}实现的是一种预编译sql,它会在原地生成一个“?”占位符,然后根据参数去替换这个占位符,如果参数类型是Integer,那么就会直接替换成参数的值,如果是字符串则会在参数值上拼接一个
' '再去替换前面的占位符,而${}采用的则是直接替换的方式。它会直接用参数来替换,如果是Integer类型还好,如果是字符串类型则需要在${}外面加上一个 ' '。例如我们创建一个根据用户名查询用户的方法:
这里使用的是#{}的方式
测试后可以发现执行成功了。
如果我们改成${},再次测试代码可以发现出问题了
因为这里的name是String类型,而${}采用的是直接替换,因此并没有将name识别成一个字符串,而是识别成字段了,从而报错了,我们在${}上加上' ':
这次没有报错了。
性能
#{}和${}在性能上会存在一定差异,#{}的性能会更高一点,为什么会更高一点,我们来看一下一个sql语句的执行过程:
- sql语句解析
- sql优化
- sql编译
- sql执行
如果一条sql是按这个流程执行完的我们称之为即时sql,使用${}的sql就是即时sql,而#{}是预编译sql,预编译sql会在第一次编译后将编译的结果缓存起来,下一次再使用时直接使用缓存里已经编译好的sql,因此预编译sql省去了语法解析优化等过程,所以#{}的sql性能会更高一些。
安全
使用${}可能会出现sql注入的问题。什么是sql注入呢?我们通过前面定义的通过名字查询用户的方法来了解一下。
我们先看一下这个方法(做了一些修改):
如果将这里username的值设置为" ' ' or 1=1",然后测试一下:
可以发现所有的用户信息都被查出来的,而正常来说,应该是一条信息也查不出来才对。这是为什么呢?前面说过${}是直接替换成参数的值的,我们来看一下替换后实际执行的sql:
可以发现实际执行的sql在我们自己编写的sql基础上发生了些许变化,新加了一个或判断,并且这个或判断是恒为true的,这也就导致where条件每次都能成立,所以用户信息都被查询出来了。而使用#{}则不会有这个问题,我们将${}换成#{}来看一下,然后再看一下执行的日志:
可以发现#{}用?替代了,并且参数为我们传入的username的值,并且这个参数会以String的形式替换掉 ? ,所以username中的or并不会识别成关键字,而是会被当作字符串的一部分,这样也就使sql注入失效了,查询到的用户的信息也是为空的。
因此在使用${}时需要注意sql注入的风险,使用#{}则不需要担心。
排序功能
在开发中,很多场景都需要对查询的数据进行排序,排序可以使用order by关键字,但在不同场景中下需要的排序方式不同,有的场景需要升序排列,而有的场景则需要降序排列,如果我们分别来实现就会显得十分冗余,因此可以在查询的方法中传入一个参数,用来控制降序或者是升序排列,这样就只需要实现一个方法了。具体如下:
如果我们要升序,就将sort的值设为sec 反之设为 desc,但这里使用#{}会出现问题,sort会被当成一个字符串从而导致sql失效并报错,所以在这里只能使用${}直接将sort替换到sort里。
模糊查询
在使用#{}进行模糊查询时会出现问题,我们通过前面根据名称查询用户的方法来看一下:
将这里改为模糊查询,改完后的sql如下:
由于#{}采用的先设置占位符再替换的方式,这样就会导致上述sql引号里再嵌套一个引号的情况,具体如下:
Preparing: select * from userinfo where username = '%'zhang'%'
这种情况不符和sql语法,从而产生报错,因此在这里通常使用${},当然,这里也不是一定不能使用#{},但使用#{}我们需要一个函数concat,加入concat的sql如下:
这样使用#{}就不会报错了。
三、数据库连接池
数据库连接池和线程池类似,数据库连接池会事先创建好几个Connection对象,如果要使用时直接去池子里拿即可。
在使用了数据库连接池的场景下,每次去连接数据库就不需要自己去创建Connection了,直接去池子里拿即可,连接完后也不需要自己去销毁连接,直接将Connection交还给连接池即可。由此可以总结出数据库连接池具有以下优点:
- 提升性能
- 节省网络资源的开销
- 减少资源重用
常见的数据库连接池有 C3P0,DBCP,Druid,Hikari,其中Hikari和Druid使用的相对较多,在Spring Boot2.x往后的版本都是以Hikari为默认的数据库连接池,而Druid由于是阿里开发的开源项目,在国内更受欢迎,并且在功能上也相对Hikari要更丰富,由此通常会将数据库连接池改为Driud,,修改成Driud的方法很简单,只需要引入Driud的依赖即可,具体为:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
然后我们再启动项目可以发现,数据库连接池已经修改成Driud了