韶光暗淡 发表于 2022-8-25 09:44:43

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

前面的话
本篇文章介绍什么是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准备一个数据表
准备工作,创建数据库与数据表。

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

-- 使用数据数据
use mycnblog;

-- 创建表[用户表]
drop table if existsuserinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default 'default.png',
    createtime datetime default now(),
    updatetime datetime default now(),
    `state` int default 1
) default charset 'utf8mb4';

-- 创建文章表
drop table if existsarticleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime datetime default now(),
    updatetime datetime default now(),
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';

-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
          vid int primary key,
          `title` varchar(250),
          `url` varchar(1000),
                createtime datetime default now(),
                updatetime datetime default now(),
          uid int
)default charset 'utf8mb4';

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

-- 文章添加测试数据
insert into articleinfo(title,content,uid)
    values('Java','Java正文',1);
   
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
</font>
下面我们主要是要userinfo表进行操作演示,目前执行上述sql后,表内的数据如下:

2.2创建项目第一步,创建SpringBoot项目。第二步,勾选上MyBatis相关依赖,包括Mybatis Framework和MySQL Driver。第三步,设置项目名称与路径。第四步,不要着急启动项目,我们还需要配置数据库数据源和MyBatis xml文件保存路径,其中这个xml文件是用来编写sql语句用的。
首先,我们创建配置文件,用来链接数据源。在企业的项目中最少有3个配置文件,一个主配置文件,另外两个配置文件分别用于开发环境和生产环境,可能还会有测试环境的配置文件,所以我们先创建配置文件,至少得有主配置文件和开发环境的配置文件。在主配置文件中应用dev配置文件和设置MyBatis xml文件的路径:<font size="3"># 当前运行环境的配置文件
spring:
profiles:
    active: dev

# 配置 mybatis xml 保存路径
mybatis:
mapper-locations: classpath:mybatis/**Mapper.xml
</font>其中**Mapper.xml只认Mapper.xml结尾的文件。
我们在开发环境中,配置数据库的url,账号与密码以及驱动,为什么要在开发环境的配置文件配置而不在主配置文件配置呢?这是因为开发环境链接的是这个数据库,但生产环境,测试环境链接的就不是开发环境中的数据库,因此我们将不同的环境使用不同的配置文件来配置,这样仅需改动主配置文件一行的代码就可以了。<font size="3"># 配置数据库信息
spring:
datasource:
    url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
    username: root
    password: 123456
    # 版本8之前版本的数据库使用 com.mysql.jdbc.Driver
    # 版本8以及之后版本的数据库使用 com.mysql.cj.jdbc.Driver
    driver-class-name: com.mysql.jdbc.Driver
</font>如果需要更方便观察有关sql的运行情况,还可以配置一些日志文件,来记录相关的日志:<font size="3"># 开启 MyBatis SQL 打印
logging:
level:
    com:
      example:
      demo: debug
mybatis:
configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
</font>这样我们的项目就创建好了,下面来介绍MyBatis的用法。
3.MyBatis的使用MyBatis的基本使用流程:创建一个使用注解@Mapper修饰的接口,该注解来自MyBatis,作用是与我们配置的Mapper.xml结尾的文件做连接。在该接口里面定义方法。在xml文件中按照MyBatis的规矩编写sql,xml文件的作用是生成数据库可执行的sql,并且能将结果映射到程序的对象中。第一步,我们定义接口,该接口在软件分层中属于Mapper层,所以我们定义在自建的mapper包下,为了提高代码的可读性和规范性,接口名称建议Mapper结尾。<font size="3">@Mapper
public interface UserMapper {
      //方法声明
}
</font>第二步,我们在该接口下声明方法,由于该方法的目的就是为了被xml文件“实现”,然后去操作数据库的,所以方法名建议与数据库的操作有关,比如我们需要根据id查询User对象的结果,我们方法名可以声明为getUserById。<font size="3">    //根据id查询
    public UserInfo getUserById(@Param(value = "id") Integer id);
</font>@Param注解中的value值表示对变量重命名,它表示该变量在xml文件下的变量名,建议该名字与数据库中的字段相对应并同名。第三步,在配置文件所规定的目录下创建Mapper.xml文件,并加上下面的代码,一般一个xml负责一个表的增删查改操作:<font size="3"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">

</mapper>
</font>当我们要编写sql时,需要在<mapper>标签里面写对应的标签来实现对数据库的操作,比如查询使用select标签,插入使用insert标签,更新使用pudate标签,删除使用delete标签等。
而在mapper标签中,我们需要设置该mapper标签随对应的接口在哪里,就是在mapper标签头中设置namespace属性,值为加包名的Mapper接口,如:<font size="3"><mapper namespace="com.example.demo.mapper.UserMapper">
</mapper>
</font>比如根据id查询用户信息,其中标签头的id属性与要实现的方法名对应,resultType表示需要映射的类是哪一个,需要写出完整的包名,这样就会将查询的结果存入到该类的对象中对对象数组中,有一点是需要注意的,那就是使用resultType映射对象一定得保证数据库中的字段名与对象中的属性名一模一样,否则不能匹配赋值。<font size="3">    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
      select * from userinfo where id=${id}
    </select>
</font>MyBatis实现的是程序中对象与数据库的映射,是程序与数据库的一座桥梁,MyBatis可以通过程序员写的代码去操作数据库,因此它处在软件分层中的Repository持久层,我们知道当后端Controller层收到前端数据库查询的请求后会去调用Service层,Service层会告诉持久层到数据库查询数据,持久层查询到数据之后,会将数据交给服务层,然后服务层会将数据交给控制层,最终控制层会将数据打包发送给前端。所以我们还需要建立服务层和控制层来构成完整的后端。对于服务层,它的任务是调用持久层的方法,去查询数据库,并获得查询的结果。<font size="3">@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    public UserInfo getUserById(Integer id) {
      return userMapper.getUserById(id);
    }
}
</font>对于控制层,它需要拿到服务层的对象,调用服务层的方法得到查询的结果。<font size="3">@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/getuserbyid")
    public UserInfo getUserById(Integer id) {
      if (id == null) return null;
      return userService.getUserById(id);
    }
}
</font>当然还少不了数据库表对于的类,如UserInfo表对应一个UserInfo类。<font size="3">@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private Integer state;
}
</font>4.使用MyBatis实现单表查询创建好Mapper接口和对应的xml文件后,我们就可以开始写数据库sql的语句了。4.1查询第一步,在Mapper接口中声明一个方法,比如通过用户ID查询用户的全部信息,就可以声明一个方法getUserById。<font size="3">public UserInfo getUserById(@Param(value = "id") Integer id);
</font>@Param注解的value参数就是将传入的变量在xml文件中的名字。
第二步,设置xml文件所映射的Mapper接口,假设我们映射了一个如下图mapper包中的UserMapper接口,该接口一定要使用@Mapper修饰,否则不是属于MyBatis中的“接口”,不能与xml产生映射。我们将mapper标签中的namespace设置为对应接口在java目录下的带包全称。<font size="3"><mapper namespace="com.example.demo.mapper.UserMapper">
</font>第三步,设置查询标签以及配置与方法的映射,查询操作使用select标签,查询标签至少设置两个属性,第一个是id表示与哪一个方法相对应,就是设置成相关联的方法的方法名,比如我们在Mapper接口中的查询方法是getUserById,则设置id="getUserById";
还有一个就是resultType我们查询需要返回查询结果UserInfo对象,如果可能有多个结果,对应的方法返回值就写List<UserInfo>,否则写UserInfo即可,但在查询标签中resultType属性只需要设置成带包名的类名即可。<font size="3"><select id="getUserById" resultType="com.example.demo.model.UserInfo">
</font>第四步,在mapper标签中编写查询sql,查询sql对应的标签就是select标签,如我们要查询id为1的用户,那么标签里面就这么写:<font size="3">    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
      select * from userinfo where id=1
    </select>
</font>但是这样写太死了,我们可以说使用预处理符#{}或者替换符${}来将程序传入的参数替换到sql语句中:<font size="3">    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
      select * from userinfo where id=${id}
      //or select * from userinfo where id=#{id}
    </select>
</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项目时,会自动添加。<font size="3"><dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
</font>第二步,我们在类中代码右键,选择Generate选项,比如我们在Mapper接口中生成单元测试的类,我们就在右键选择Generate选项。然后会跳出一个框,选择Test。第三步,进行配置Test,和选择需要测试的方法。第四步,第三步勾选上需要测试的方法后,会在test目录生成一个同级的目录,会自动生成测试类和测试方法,当然我们也能够手动地写,创建的测试类就在原来类名最后加上一个Test即可。单元测试的类创建在如图目录下:
[*]配置单元测试的类,在类上加上@SpringBootTest注解
[*]添加测试方法,并加上@Test注解
[*]通过注入Mapper对象,调用里面操作数据库的方法,进行单元测试。





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

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

不通过显示:

4.3修改现数据库有以下的数据:现在我们演示如何使用MyBatis根据id修改对应用户名username,其实和查询操作是一样的,甚至还要更简单一点,而对于结果的返回值,我们返回受影响的行数,此时我们是不用设置resultType的。第一步,声明方法:<font size="3">    //根据id修改名字
    public Integer updateName(@Param(value = "id") Integer id, @Param(value = "username") String username);
</font>第二步,在xml中写sql,修改操作使用update标签。<font size="3">    <update id="updateName" >
      update userinfo set username=#{username} where id=#{id}
    </update>
</font>我们将id=2的用户名修改为李四,并且使用@Transactional注解可以防止污染数据库,实现原理就是利用了数据库事务的回滚,代码:<font size="3">    @Test
    @Transactional
    void updateName() {
      int result = userMapper.updateName(2, "李四");
      Assertions.assertSame(1,result);
    }
</font>单元测试结果:但是数据库并没有发生修改,原因是@Transactional注解进行了事务的回滚,但是自增主键不会进行回滚,比如你进行了id为3用户插入操作,事务回滚了,下一次插入自增主键id的值为4:这也是使用单元测试为什么不会污染数据库的原因。4.4删除删除其实和查询,修改也是一样的,我们来演示将id为2的数据删除。
第一步,声明方法:<font size="3">    //根据id删除用户
    public Integer delById(@Param(value = "id") Integer id);
</font>第二步,在xml文件编写sql,删除操作使用delete标签:<font size="3">    <delete id="delById">
      delete from userinfo where id=#{id}
    </delete>
</font>第三步,编写单元测试验证:<font size="3">    @Test
    @Transactional
    void delById() {
      int result = userMapper.delById(2);
      Assertions.assertSame(1,result);
    }
</font>第四步,查询单元测试结果:4.5插入该操作相比于之前的查询,修改和删除操作要复杂一点,但其实也差不多,为了提高代码的通用性,我们声明方法中的形参传入一个UserInfo对象。首先,我们来演示插入一条数据,返回一个受影响的行数。第一步,声明方法:<font size="3">    //添加用户,返回影响行数
    public Integer add(UserInfo userInfo);
</font>第二步,编写sql语句,插入操作使用insert标签。<font size="3">    <insert id="add">
      insert into userinfo(username, password, photo) values (#{username}, #{password},#{photo});
    </insert>
</font>第三步,编写单元测试,这次我们不用@Transactional注解,直接看数据库明显一点。<font size="3">    @Test
    void add() {
      UserInfo userInfo = new UserInfo();
      userInfo.setUsername("老六");
      userInfo.setPassword("888");
      int result = userMapper.add(userInfo);
      Assertions.assertSame(1,result);
    }
</font>第四步,验证结果,单元测试结果:数据库查询结果:其实,Mybatis也可以返回多个参数,比如返回受影响的行数和自增id的值,这个时候我们就需要对xml的insert标签进行配置。前面声明方法还是需要的:<font size="3">    public Integer addGetId(UserInfo userInfo);
</font>xml的insert标签中还需要设置useGeneratedKeys="true"表示为是否自增主键,keyProperty="id"表示自增主键是哪一个。<font size="3">    <insert id="addGetId" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
      insert into userinfo(username, password, photo) values (#{username}, #{password},#{photo});
    </insert>

</font>单元测试方法:<font size="3">    @Test
    void addGetId() {
      UserInfo userInfo = new UserInfo();
      userInfo.setUsername("老六");
      userInfo.setPassword("888");
      userInfo.setPhoto("default.png");
      System.out.println("插入之前的id=" + userInfo.getId());
      int result = userMapper.addGetId(userInfo);
      System.out.println("插入之后的id=" + userInfo.getId());
      Assertions.assertSame(1,result);
    }
</font>单元测试结果:数据库查询结果:


页: [1]
查看完整版本: 单元测试中如何使用MyBatis框架,以及如何配置框架?