51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 503|回复: 0
打印 上一主题 下一主题

[资料] 单元测试中如何使用MyBatis框架,以及如何配置框架?

[复制链接]
  • TA的每日心情
    无聊
    2024-1-24 09:21
  • 签到天数: 27 天

    连续签到: 2 天

    [LV.4]测试营长

    跳转到指定楼层
    1#
    发表于 2022-8-25 09:44:43 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    前面的话
    本篇文章介绍什么是MyBatis?MyBatis的优点,如何配置和使用MyBatis,使用MyBatis实现简单的增删查改功能。
    1.MyBatis概述1.1什么是MyBatis

    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。

    MyBatis工作原理:就是对JDBC进行了封装。

    MyBatis主要依赖于下面几个组件:
    1、SQLSessionFactoryBuilder(构造器):它会根据配置信息或者代码生成SqlSessionFactory。
    2、SqlSessionFactory(工厂接口):依靠工厂生成SqlSession。
    3、SqlSession(会话):是一个既可以发送SQL去执行并且返回结果,也可以获取Mapper接口。
    4、SQL Mapper:是由一个JAVA接口和XML文件(或注解)构成,需要给出对应的SQL和映射规则。SQL是由Mapper发送出去,并且返回结果。


    所以我们使用MyBatis的时候需要创建Mapper层接口以及对应的xml文件,注解可以代替xml文件进行写SQL,但是SQL语句复杂的时候使用注解写SQL很奇怪,本文着重介绍使用xml文件写SQL。

    1.2MyBatis的优点
    MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。MyBatis是对JDBC的封装。相对于JDBC,MyBatis有以下优点:

    优化获取和释放
    SQL统一管理,对数据库进行存取操作
    支持生成动态SQL语句,针对非必传参数可以动态调整SQL
    能够对结果集进行映射

    2.MyBatis项目的创建
    2.1准备一个数据表

    准备工作,创建数据库与数据表。

    1. <font size="3">-- 创建数据库
    2. drop database if exists mycnblog;
    3. create database mycnblog DEFAULT CHARACTER SET utf8mb4;

    4. -- 使用数据数据
    5. use mycnblog;

    6. -- 创建表[用户表]
    7. drop table if exists  userinfo;
    8. create table userinfo(
    9.     id int primary key auto_increment,
    10.     username varchar(100) not null,
    11.     password varchar(32) not null,
    12.     photo varchar(500) default 'default.png',
    13.     createtime datetime default now(),
    14.     updatetime datetime default now(),
    15.     `state` int default 1
    16. ) default charset 'utf8mb4';

    17. -- 创建文章表
    18. drop table if exists  articleinfo;
    19. create table articleinfo(
    20.     id int primary key auto_increment,
    21.     title varchar(100) not null,
    22.     content text not null,
    23.     createtime datetime default now(),
    24.     updatetime datetime default now(),
    25.     uid int not null,
    26.     rcount int not null default 1,
    27.     `state` int default 1
    28. )default charset 'utf8mb4';

    29. -- 创建视频表
    30. drop table if exists videoinfo;
    31. create table videoinfo(
    32.           vid int primary key,
    33.           `title` varchar(250),
    34.           `url` varchar(1000),
    35.                 createtime datetime default now(),
    36.                 updatetime datetime default now(),
    37.           uid int
    38. )default charset 'utf8mb4';

    39. -- 添加一个用户信息
    40. INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
    41. (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

    42. -- 文章添加测试数据
    43. insert into articleinfo(title,content,uid)
    44.     values('Java','Java正文',1);
    45.    
    46. -- 添加视频
    47. insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
    48. </font>
    复制代码

    下面我们主要是要userinfo表进行操作演示,目前执行上述sql后,表内的数据如下:

    2.2创建项目

    第一步,创建SpringBoot项目。

    第二步,勾选上MyBatis相关依赖,包括Mybatis Framework和MySQL Driver。

    第三步,设置项目名称与路径。

    第四步,不要着急启动项目,我们还需要配置数据库数据源和MyBatis xml文件保存路径,其中这个xml文件是用来编写sql语句用的。


    首先,我们创建配置文件,用来链接数据源。

    在企业的项目中最少有3个配置文件,一个主配置文件,另外两个配置文件分别用于开发环境和生产环境,可能还会有测试环境的配置文件,所以我们先创建配置文件,至少得有主配置文件和开发环境的配置文件。

    在主配置文件中应用dev配置文件和设置MyBatis xml文件的路径:

    1. <font size="3"># 当前运行环境的配置文件
    2. spring:
    3.   profiles:
    4.     active: dev

    5. # 配置 mybatis xml 保存路径
    6. mybatis:
    7.   mapper-locations: classpath:mybatis/**Mapper.xml
    8. </font>
    复制代码

    其中**Mapper.xml只认Mapper.xml结尾的文件。


    我们在开发环境中,配置数据库的url,账号与密码以及驱动,为什么要在开发环境的配置文件配置而不在主配置文件配置呢?这是因为开发环境链接的是这个数据库,但生产环境,测试环境链接的就不是开发环境中的数据库,因此我们将不同的环境使用不同的配置文件来配置,这样仅需改动主配置文件一行的代码就可以了。

    1. <font size="3"># 配置数据库信息
    2. spring:
    3.   datasource:
    4.     url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
    5.     username: root
    6.     password: 123456
    7.     # 版本8之前版本的数据库使用 com.mysql.jdbc.Driver
    8.     # 版本8以及之后版本的数据库使用 com.mysql.cj.jdbc.Driver
    9.     driver-class-name: com.mysql.jdbc.Driver
    10. </font>
    复制代码

    如果需要更方便观察有关sql的运行情况,还可以配置一些日志文件,来记录相关的日志:

    1. <font size="3"># 开启 MyBatis SQL 打印
    2. logging:
    3.   level:
    4.     com:
    5.       example:
    6.         demo: debug
    7. mybatis:
    8.   configuration:
    9.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    10. </font>
    复制代码

    这样我们的项目就创建好了,下面来介绍MyBatis的用法。


    3.MyBatis的使用

    MyBatis的基本使用流程:

    创建一个使用注解@Mapper修饰的接口,该注解来自MyBatis,作用是与我们配置的Mapper.xml结尾的文件做连接。

    在该接口里面定义方法。

    在xml文件中按照MyBatis的规矩编写sql,xml文件的作用是生成数据库可执行的sql,并且能将结果映射到程序的对象中。

    第一步,我们定义接口,该接口在软件分层中属于Mapper层,所以我们定义在自建的mapper包下,为了提高代码的可读性和规范性,接口名称建议Mapper结尾。

    1. <font size="3">@Mapper
    2. public interface UserMapper {
    3.         //方法声明
    4. }
    5. </font>
    复制代码

    第二步,我们在该接口下声明方法,由于该方法的目的就是为了被xml文件“实现”,然后去操作数据库的,所以方法名建议与数据库的操作有关,比如我们需要根据id查询User对象的结果,我们方法名可以声明为getUserById。

    1. <font size="3">    //根据id查询
    2.     public UserInfo getUserById(@Param(value = "id") Integer id);
    3. </font>
    复制代码

    @Param注解中的value值表示对变量重命名,它表示该变量在xml文件下的变量名,建议该名字与数据库中的字段相对应并同名。

    第三步,在配置文件所规定的目录下创建Mapper.xml文件,并加上下面的代码,一般一个xml负责一个表的增删查改操作:

    1. <font size="3"><?xml version="1.0" encoding="UTF-8"?>
    2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="com.example.demo.mapper.UserMapper">

    4. </mapper>
    5. </font>
    复制代码

    当我们要编写sql时,需要在<mapper>标签里面写对应的标签来实现对数据库的操作,比如查询使用select标签,插入使用insert标签,更新使用pudate标签,删除使用delete标签等。


    而在mapper标签中,我们需要设置该mapper标签随对应的接口在哪里,就是在mapper标签头中设置namespace属性,值为加包名的Mapper接口,如:

    1. <font size="3"><mapper namespace="com.example.demo.mapper.UserMapper">
    2. </mapper>
    3. </font>
    复制代码

    比如根据id查询用户信息,其中标签头的id属性与要实现的方法名对应,resultType表示需要映射的类是哪一个,需要写出完整的包名,这样就会将查询的结果存入到该类的对象中对对象数组中,有一点是需要注意的,那就是使用resultType映射对象一定得保证数据库中的字段名与对象中的属性名一模一样,否则不能匹配赋值。

    1. <font size="3">    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
    2.         select * from userinfo where id=${id}
    3.     </select>
    4. </font>
    复制代码

    MyBatis实现的是程序中对象与数据库的映射,是程序与数据库的一座桥梁,MyBatis可以通过程序员写的代码去操作数据库,因此它处在软件分层中的Repository持久层,我们知道当后端Controller层收到前端数据库查询的请求后会去调用Service层,Service层会告诉持久层到数据库查询数据,持久层查询到数据之后,会将数据交给服务层,然后服务层会将数据交给控制层,最终控制层会将数据打包发送给前端。

    所以我们还需要建立服务层和控制层来构成完整的后端。

    对于服务层,它的任务是调用持久层的方法,去查询数据库,并获得查询的结果。

    1. <font size="3">@Service
    2. public class UserService {

    3.     @Resource
    4.     private UserMapper userMapper;

    5.     public UserInfo getUserById(Integer id) {
    6.         return userMapper.getUserById(id);
    7.     }
    8. }
    9. </font>
    复制代码

    对于控制层,它需要拿到服务层的对象,调用服务层的方法得到查询的结果。

    1. <font size="3">@RestController
    2. @RequestMapping("/user")
    3. public class UserController {
    4.     @Autowired
    5.     private UserService userService;

    6.     @RequestMapping("/getuserbyid")
    7.     public UserInfo getUserById(Integer id) {
    8.         if (id == null) return null;
    9.         return userService.getUserById(id);
    10.     }
    11. }
    12. </font>
    复制代码

    当然还少不了数据库表对于的类,如UserInfo表对应一个UserInfo类。

    1. <font size="3">@Data
    2. public class UserInfo {
    3.     private Integer id;
    4.     private String username;
    5.     private String password;
    6.     private String photo;
    7.     private String createtime;
    8.     private String updatetime;
    9.     private Integer state;
    10. }
    11. </font>
    复制代码
    4.使用MyBatis实现单表查询

    创建好Mapper接口和对应的xml文件后,我们就可以开始写数据库sql的语句了。

    4.1查询

    第一步,在Mapper接口中声明一个方法,比如通过用户ID查询用户的全部信息,就可以声明一个方法getUserById。

    1. <font size="3">public UserInfo getUserById(@Param(value = "id") Integer id);
    2. </font>
    复制代码

    @Param注解的value参数就是将传入的变量在xml文件中的名字。


    第二步,设置xml文件所映射的Mapper接口,假设我们映射了一个如下图mapper包中的UserMapper接口,该接口一定要使用@Mapper修饰,否则不是属于MyBatis中的“接口”,不能与xml产生映射。

    我们将mapper标签中的namespace设置为对应接口在java目录下的带包全称。

    1. <font size="3"><mapper namespace="com.example.demo.mapper.UserMapper">
    2. </font>
    复制代码

    第三步,设置查询标签以及配置与方法的映射,查询操作使用select标签,查询标签至少设置两个属性,第一个是id表示与哪一个方法相对应,就是设置成相关联的方法的方法名,比如我们在Mapper接口中的查询方法是getUserById,则设置id="getUserById";


    还有一个就是resultType我们查询需要返回查询结果UserInfo对象,如果可能有多个结果,对应的方法返回值就写List<UserInfo>,否则写UserInfo即可,但在查询标签中resultType属性只需要设置成带包名的类名即可。

    1. <font size="3"><select id="getUserById" resultType="com.example.demo.model.UserInfo">
    2. </font>
    复制代码

    第四步,在mapper标签中编写查询sql,查询sql对应的标签就是select标签,如我们要查询id为1的用户,那么标签里面就这么写:

    1. <font size="3">    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
    2.         select * from userinfo where id=1
    3.     </select>
    4. </font>
    复制代码

    但是这样写太死了,我们可以说使用预处理符#{}或者替换符${}来将程序传入的参数替换到sql语句中:

    1. <font size="3">    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
    2.         select * from userinfo where id=${id}
    3.         //or select * from userinfo where id=#{id}
    4.     </select>
    5. </font>
    复制代码

    其中id=${id}相当于id=传入的id变量的值,比如id=1,则${id}会被替换为1,使用该方法存在SQL注入的问题,并且传入字符串等其他非数值类型可能会出现问题,而id=#{id}相当于id=?,相当于替换成一个占位符,然后会将传入的id通过占位符的形式插入到sql语句中,可以防止SQL注入问题,并且适用于所有类型的变量。


    有关${}与#{}我会另写一篇文章专门来介绍,它们之间的区别,以及它们各自适合的应用场景。


    经过以上步骤我们的sql就写好了,由于我们的id是唯一的,所以查询到的结果也是唯一的,我们以id=1进行查询演示,目前数据库的数据如下:

    我们启动程序,访问127.0.0.1:8080/user/getuserbyid通过查询字符串的形式将id=1传递给后端进行数据库的查询。

    我们得到的结果如下:

    在控制台也有执行的sql和结果。

    4.2单元测试

    4.2.1单元测试概述

    单元测试优点:

    1、可以非常简单、直观、快速的测试某个功能是否正确。

    2、使用单元测试可以帮我们在打包的时候,发现一些问题,因为在打包之前,所以的单元测试必须通过,否则不能打包成功。

    3、使用单元测试,在测试功能的时候,可以不污染连接的数据库,也就是可以不对数据库进行任何改变的情况下,测试功能的正确性。


    4.2.2SpringBoot单元测试的使用

    第一步,添加框架的支持,创建spring boot项目时,会自动添加。

    1. <font size="3">  <dependency>
    2.                         <groupId>org.springframework.boot</groupId>
    3.                         <artifactId>spring-boot-starter-test</artifactId>
    4.                         <scope>test</scope>
    5.                 </dependency>
    6. </font>
    复制代码

    第二步,我们在类中代码右键,选择Generate选项,比如我们在Mapper接口中生成单元测试的类,我们就在右键选择Generate选项。

    然后会跳出一个框,选择Test。

    第三步,进行配置Test,和选择需要测试的方法。

    第四步,第三步勾选上需要测试的方法后,会在test目录生成一个同级的目录,会自动生成测试类和测试方法,当然我们也能够手动地写,创建的测试类就在原来类名最后加上一个Test即可。

    单元测试的类创建在如图目录下:

    • 配置单元测试的类,在类上加上@SpringBootTest注解
    • 添加测试方法,并加上@Test注解
    • 通过注入Mapper对象,调用里面操作数据库的方法,进行单元测试。





    除了直接输出运行的结果,还可以通过断言来判断测试是否通过,常见断言的方法有如下这一些:

    如通过判断返回的对象引用是否不为null来判断:
    1. <font size="3">    @Autowired
    2.     private UserMapper userMapper;
    3.     @Test
    4.     void gerUserById() {
    5.         UserInfo userInfo = userMapper.getUserById(1);
    6.         //System.out.println(userInfo);
    7.         Assertions.assertNotNull(userInfo);
    8.     }
    9. </font>
    复制代码
    正常通过显示:

    不通过显示:

    4.3修改

    现数据库有以下的数据:

    现在我们演示如何使用MyBatis根据id修改对应用户名username,其实和查询操作是一样的,甚至还要更简单一点,而对于结果的返回值,我们返回受影响的行数,此时我们是不用设置resultType的。

    第一步,声明方法:

    1. <font size="3">    //根据id修改名字
    2.     public Integer updateName(@Param(value = "id") Integer id, @Param(value = "username") String username);
    3. </font>
    复制代码

    第二步,在xml中写sql,修改操作使用update标签。

    1. <font size="3">    <update id="updateName" >
    2.         update userinfo set username=#{username} where id=#{id}
    3.     </update>
    4. </font>
    复制代码

    我们将id=2的用户名修改为李四,并且使用@Transactional注解可以防止污染数据库,实现原理就是利用了数据库事务的回滚,代码:

    1. <font size="3">    @Test
    2.     @Transactional
    3.     void updateName() {
    4.         int result = userMapper.updateName(2, "李四");
    5.         Assertions.assertSame(1,result);
    6.     }
    7. </font>
    复制代码

    单元测试结果:

    但是数据库并没有发生修改,原因是@Transactional注解进行了事务的回滚,但是自增主键不会进行回滚,比如你进行了id为3用户插入操作,事务回滚了,下一次插入自增主键id的值为4:

    这也是使用单元测试为什么不会污染数据库的原因。

    4.4删除

    删除其实和查询,修改也是一样的,我们来演示将id为2的数据删除。
    第一步,声明方法:

    1. <font size="3">    //根据id删除用户
    2.     public Integer delById(@Param(value = "id") Integer id);
    3. </font>
    复制代码

    第二步,在xml文件编写sql,删除操作使用delete标签:

    1. <font size="3">    <delete id="delById">
    2.         delete from userinfo where id=#{id}
    3.     </delete>
    4. </font>
    复制代码

    第三步,编写单元测试验证:

    1. <font size="3">    @Test
    2.     @Transactional
    3.     void delById() {
    4.         int result = userMapper.delById(2);
    5.         Assertions.assertSame(1,result);
    6.     }
    7. </font>
    复制代码

    第四步,查询单元测试结果:

    4.5插入

    该操作相比于之前的查询,修改和删除操作要复杂一点,但其实也差不多,为了提高代码的通用性,我们声明方法中的形参传入一个UserInfo对象。

    首先,我们来演示插入一条数据,返回一个受影响的行数。

    第一步,声明方法:

    1. <font size="3">    //添加用户,返回影响行数
    2.     public Integer add(UserInfo userInfo);
    3. </font>
    复制代码

    第二步,编写sql语句,插入操作使用insert标签。

    1. <font size="3">    <insert id="add">
    2.         insert into userinfo(username, password, photo) values (#{username}, #{password},#{photo});
    3.     </insert>
    4. </font>
    复制代码

    第三步,编写单元测试,这次我们不用@Transactional注解,直接看数据库明显一点。

    1. <font size="3">    @Test
    2.     void add() {
    3.         UserInfo userInfo = new UserInfo();
    4.         userInfo.setUsername("老六");
    5.         userInfo.setPassword("888");
    6.         int result = userMapper.add(userInfo);
    7.         Assertions.assertSame(1,result);
    8.     }
    9. </font>
    复制代码

    第四步,验证结果,单元测试结果:

    数据库查询结果:

    其实,Mybatis也可以返回多个参数,比如返回受影响的行数和自增id的值,这个时候我们就需要对xml的insert标签进行配置。

    前面声明方法还是需要的:

    1. <font size="3">    public Integer addGetId(UserInfo userInfo);
    2. </font>
    复制代码

    xml的insert标签中还需要设置useGeneratedKeys="true"表示为是否自增主键,keyProperty="id"表示自增主键是哪一个。

    1. <font size="3">    <insert id="addGetId" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    2.         insert into userinfo(username, password, photo) values (#{username}, #{password},#{photo});
    3.     </insert>

    4. </font>
    复制代码

    单元测试方法:

    1. <font size="3">    @Test
    2.     void addGetId() {
    3.         UserInfo userInfo = new UserInfo();
    4.         userInfo.setUsername("老六");
    5.         userInfo.setPassword("888");
    6.         userInfo.setPhoto("default.png");
    7.         System.out.println("插入之前的id=" + userInfo.getId());
    8.         int result = userMapper.addGetId(userInfo);
    9.         System.out.println("插入之后的id=" + userInfo.getId());
    10.         Assertions.assertSame(1,result);
    11.     }
    12. </font>
    复制代码

    单元测试结果:

    数据库查询结果:




    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-5-7 22:27 , Processed in 0.071478 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表