目录
单元测试
单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证的过程就叫做单元测试.
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的,很明确的代码功能是否正确.执行单元测试就是为了证明某段代码的执行结果是否符合我们的预期.如果测试结果符合我们的预期,称之为测试通过,否则就是测试未通过.
单元测试的好处
- 可以简单直观快速的测试某一个功能是否正确.
- 使用单元测试可以帮我们在打包的时候,发现一些问题,因为在打包之前,所有的单元测试必须通过,否则不能打包成功.
- 使用单元测试,在测试功能的时候,可以不污染数据库中的数据,也就是说可以不对数据库进行任何改变的情况下来测试功能.
- 可以跳过系统限制(比如登录校验),直接测试代码功能.
spring boot的单元测试
Spring Boot项目创建的时候会默认帮我们添加单元测试的框架.
这个单元测试的框架主要是依靠另一个著名的测试框架JUnit实现的.
spring boot的单元测试的使用
我们要测试UserMapper中的getUserById这个方法.
1.在要测试的类里,右键点击生成
2.点击test
3.配置测试的信息,点击ok
4.在生成的测试类里,加注解,写测试代码
5.运行单元测试
6.查看测试结果
追加测试方法
如果要在生成userMapper中的getall方法,还是按照上述的方式,只不过在生成测试类的时候,会报一个error,点击ok即可.
断言
单元测试也可以搭配断言来使用.
在掌握了单元测试之后,我们就可以在写MyBatis的时候,边写边测试.
MyBatis
单表传参查询
@Param就是给传递的参数起名字,当里面写成uid的时候,xml里也要用${uid}来接收.
MyBatis获取动态参数有两种实现
1.${paramName} ,这种方式是直接替换.
2.#{paramName},这种方式是采用占位符的模式.
验证上述两种模式,我们可以在配置文件里进行配置,来打印MyBatis执行的sql
直接替换:
占位符:
两者的区别:
在使用上,传递一个int的类型的数据,看不出两者的区别,我们来传递一个varchar类型的数据.
根据名称来查询用户:
先使用占位符的方式:
一切正常.
使用直接替换的方式:
报错了,因为是直接替换
生成的sql里张三没有加上单引号,所以报错了.
因为是直接替换,在查询varchar类型的数据的时候,由于没有单引号,所以会报错.
解决这个问题,我们可以手动加上单引号,这样可以不报错,但是直接替换不能保证我们的安全问题.
安全问题sql注入
sql注入时常发生在登录中,用了一个不正确的用户密码,但是依然查询到了数据,所以我们模拟一个登录环境:
当我们使用#{}时,是安全的,不会发生sql注入.我们改成${},看sql注入是如何发生的.
为了方便演示,我们数据库中的用户表中的内容只有一条数据.
生成测试代码,看其能否正确查询到用户信息:
正确的用户名密码可以查询到,错误的用户密码查询不到.
接下来,把密码设置成比较奇怪的一段字符:
这就发生了sql注入.
但是我们将${}改为#{}就没有这个问题:
这就说明${}存在安全性问题,#{}没有安全问题.
#{}采用的是预执行,而${}采用的是及时执行.#{}没有sql注入的问题,但是它是识别不了sql关键字的,当我们有些场景需要传递sql关键字的时候,使用#{}就不行了.
降序升序传递的就是sql的关键字..
使用#{}就会报错.
单表修改操作
userMapper:
xml:
单元测试:
如果想让测试的数据不污染数据库,在单元测试方法上面加一个注解即可.
数据库的数据没有改变. 执行完之后进行一个回滚操作.
单表删除操作
单表添加操作
我们在添加用户的时候,除了返回受影响的行数之外,还希望得到添加的用户的id,这个应该怎么实现
呢?
单元测试的结果:
useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据
like模糊匹配查询
我们实现根据用户名来模糊查询.
在UserMapper里实现方法声明:
在UserMapper.xml里实现方法的具体实现:
生成单元测试代码并启动:
启动之后发现报错了.
报错的原因就是:我们使用#{}是占位符的方式
所以,它会根据我们传递的参数类型来判断是否加单引号,由于我们传递的是String类型,所以最终生成的sql:
解决这个问题,我们可以使用mysql的内置函数concat,它的作用就是实现字符串的拼接.
返回字典映射resultMap
当实体类中的属性名称(pwd)和数据库中表的字段名称(password)不一致时,Mybatis就无法实现属性和字段的映射了.
在此种场景下,我们可以使用resultMap字典映射.此外resultMap也可以应用在一对一和一对多的关系查询中.
resultMap定义是在xml中的.
启动单元测试:
虽然名称不一致,但是由于我们设定了字典映射,所以返回的password依然能够映射到pwd上.
面对属性字段名称不一致的情况,我们还可以使用mysql的重命名,对查询的结果集的字段进行重命名.
多表查询
实现通过文章的id来查询文章表的详情页查询,除了要查询文章表的字段之外,还要知道文章的作者是谁.
创建一个ArticleInfo实体类
创建一个 ArticleInfoVO类,因为我们要查询的内容不只是文章表的,所以要使用到vo(value object).
vo中文翻译为'值对象',是一种特殊的Java Bean,与Entity相比,vo的作用更多是用于传递一些特定的数据,而不是表现整个业务实体数据.其属性通常是只读的.vo的属性通常是多个表或者其他来源手机并组装成的,用于集合多表数据.
我们只需要让其继承自ArticleInfo,在加一个username 属性即可,不必在把ArticleInfo的属性在写一遍.
创建ArticleMapper,加@Mapper注解,声明方法
创建 ArticleMapper.xml,进行方法的具体实现
生成单元测试,加@SpringBootTest注解,引入ArticleMapper
启动单元测试
发现只打印了子类的属性,没有打印父类的属性.
出现这个问题的原因就是,我们使用了lombok的@Data注解,生成的子类的toString方法默认是不打印父类属性的.这一点我们可以查看生成的target来验证.
解决这个问题.只需要重写toString方法即可.当我们重写toString之后,lombok的toString就会不起作用.
右键生成,点击toString(),选择本类+父类的模板.点击ok.
再次启动单元测试:
动态SQL的使用
动态 sql 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接 .
就是允许在xml里面写逻辑判断.不同的执行生成的sql可能不同.
为什么要使用动态sql
在添加用户的时候会有这么一个场景:
必填字段和非必填字段.对于非必填字段,用户填入了,就在数据库正常插入即可.如果用户没有填,那么传过来就是null,如果不使用动态sql,在数据库中就插入了null值,但是我们期望的是用户不填我们就使用该字段的默认值,而不是null值.
我们就拿用户表中的state字段举例:
默认值是1.
if标签
模拟添加用户的时候photo是非必传的 .
方法实现:
生成单元测试:
不传photo
传photo:
两次生成的sql是不同的,这就是动态sql.
trim标签
<trim>标签中有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀
where标签
set标签
set标签用来修改操作的时候,它会帮我们自动生成set(set标签内容不为空的时候),也会帮我们自动去除逗号后缀.
foreach标签
对集合进⾏遍历时可以使⽤该标签。<foreach>标签有如下属性:
collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
delete from article where id in (1,2,3);