软件测试修炼之路 A Tester

Mybatis

2023-08-13
i.itest.ren

MyBatis 本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

总之,Mybatis对JDBC访问数据库的过程进行了封装,简化了JDBC代码,解决JDBC将结果集封装为Java对象的麻烦。

一、Mybatis简介

什么是MyBatis

MyBatis 本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

总之,Mybatis对JDBC访问数据库的过程进行了封装,简化了JDBC代码,解决JDBC将结果集封装为Java对象的麻烦。

下图是MyBatis架构图:

v2-bc338a28008c9e0fa50deb6010ddf95c_r

  • (1)mybatis-config.xml是Mybatis的核心配置文件,通过其中的配置可以生成SqlSessionFactory,也就是SqlSession工厂
  • (2)基于SqlSessionFactory可以生成SqlSession对象
  • (3)SqlSession是一个既可以发送SQL去执行,并返回结果,类似于JDBC中的Connection对象,也是Mybatis中至关重要的一个对象。
  • (4)Executor是SqlSession底层的对象,用于执行SQL语句
  • (5)MapperStatement对象也是SqlSession底层的对象,用于接收输入映射(SQL语句中的参数),以及做输出映射(即将SQL查询的结果映射成相应的结果)

为什么要使用MyBatis

原始的JDBC操作

谈及mybatis,必然需要先了解Java和数据库的连接技术——JDBC(Java DataBase Connectivity)。

jdbc代码:

try {
    Class.forName("com.mysql.jdbc.Driver");
    try {
        connection = DriverManager.getConnection("jdbc:mysql://db.itest.ren:3306/testdb", "xxxx", "xxxx");

        String sql = "Select * from testTable WHERE orderid = '" + orderId + "'";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.execute();

        try {
            resultSet = preparedStatement.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            while (resultSet.next()) {
                sellerId = resultSet.getLong("sid");
                list.add(sellerId);

            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

    } catch (SQLException e) {
        System.out.println("连接数据库失败");
        e.printStackTrace();
    }
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
  • 1、使用传统方式JDBC访问数据库:
    • (1)使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);
    • (2)JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;
    • (3)SQL是写死在程序中,一旦修改SQL,需要对类重新编译;
    • (4)对查询SQL执行后返回的ResultSet对象,需要手动处理,有时会特别麻烦;
  • 2、使用mybatis框架访问数据库:
    • (1)Mybatis对JDBC对了封装,可以简化JDBC代码;
    • (2)Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;
    • (3)Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。
    • (4)对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。

总之,JDBC中所有的问题(代码繁琐、有太多重复代码、需要操作太多对象、释放资源、对结果的处理太麻烦等),在Mybatis框架中几乎都得到了解决!!

二、MyBatis快速入门 需求:使用mybatis查询表中的所有数据

表信息:

CREATE TABLE `emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `job` varchar(50) DEFAULT NULL,
  `salary` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8

pom.xml

<dependencies>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.9.10</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.18</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.2.8</version>
    </dependency>
</dependencies>

resource/mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>
    <!-- 默认指定的数据库环境为MySQL,引用其中一个environment元素的id -->
    <environments default="mysql">
        <!-- 配置名为mysql(名称可任意取)的环境 -->
        <environment id="mysql">
            <!-- 配置事务管理器,JDBC代表使用JDBC自带的事务提交和回滚 -->
            <transactionManager type="JDBC"/>
            <!-- datasource配置数据源,此处使用MyBatis内置的数据源 -->
            <dataSource type="POOLED">
                <!-- 配置连接数据库的驱动、URL、用户名和密码 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://db.itest.ren:3306/testdb"/>
                <property name="username" value="xxxxx"/>
                <property name="password" value="xxxxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 配置MyBatis需要加载的Mapper -->
        <mapper resource="EmpMapper.xml"/>
    </mappers>
</configuration>

编写实体类pojo/Emp.java:

public class Emp {

    //1.声明实体类中的属性
    private Integer id;

    private String name;

    private String job;

    private Double salary;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", salary=" + salary +
                '}';
    }
}

编写Mapper文件EmpMapper.xml

<?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="EmpMapper">
    <select id="findAll" resultType="com.itest.mybatis.pojo.Emp">
        select * from emp
    </select>
</mapper>

编写测试代码访问所有数据:

public class TestMybatis01 {

    /** 练习1(快速入门):  查询emp表中的所有员工, 返回一个List<Emp>集合
 * @throws IOException */

    @Test
    public void findAll() throws IOException {

        //1.读取mybatis的核心配置文件(mybatis-config.xml)
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

        //2.通过配置信息获取一个SqlSessionFactory工厂对象
        SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);

        //3.通过工厂获取一个SqlSession对象
        SqlSession session = fac.openSession();

        //4.通过namespace+id找到要执行的sql语句并执行sql语句
        List<Emp> list = session.selectList("EmpMapper.findAll");

        //5.输出结果
        for(Emp e : list) {
            System.out.println( e );
        }
    }
}

输出如下:

Emp{id=1, name='李四', job='程序员', salary=2800.0}
Emp{id=2, name='王五', job='程序员鼓励师', salary=2700.0}
Emp{id=3, name='王二', job='部门总监', salary=4200.0}
Emp{id=4, name='麻子', job='程序员', salary=3000.0}
Emp{id=5, name='高启强', job='程序员', salary=3500.0}

三、配置文件

3.1 mybatis-config.xml配置文件

详细参考:https://mybatis.org/mybatis-3/zh/configuration.html

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

  • configuration(配置)
  • properties(属性)
  • settings(设置)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境配置)
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

3.1.1 environments环境配置

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:

每个数据库对应一个 SqlSessionFactory 实例

为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。

3.1.2 事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。推荐使用
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。不推荐使用

3.1.3 数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

有三种内建的数据源类型(也就是 type=”[UNPOOLED POOLED JNDI]”):
  • UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。
  • POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
  • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

3.1.4 映射器(mappers)

用于导入mapper文件的位置,其中可以配置多个mapper,即可以导入多个mapper文件

可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等

3.2 Mapper.xml配置文件

详细参考:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

3.2.1 上述项目中的EmpMapper.xml配置文件解释

(1)第1行是xml的文档声明,用于声明xml的版本和编码

(2)第2、3、4行,引入了xml约束文档,当前xml文档将会按照mybatis-3-mapper.dtd文件所要求的规则进行书写。

(3)Mapper标签:根标签,其中namespace(名称空间,也叫命名空间),要求不能重复。在程序中通过【namespace + id 】定位到要执行哪一条SQL语句

(4)select标签:用于指定将来要执行的各种SQL语句。标签上可以声明属性,下面介绍常用的属性:id、resultType、resultMap

  • id属性:要求值不能重复。将来在执行SQL时,可以通过【namespace + id】找到指定SQL并执行。
  • resultType属性:从这条SQL语句中返回所期望类型的类的完全限定名称(包名+类名)。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。 简而言之,resultType控制查询SQL执行后返回值的类型或集合中的泛型,例如查询emp表中的单条记录,返回值是一个Emp对象,因此,resultType=”com.itest.mybatis.pojo.Emp”; 如果查询emp表中的多条记录,返回值是一个List,此时resultType的值应该集合中的泛型,因此resultType=”com.itest.mybatis.pojo.Emp”;
  • resultMap属性:复杂对象结构(例如多表关联查询等)。 使用 resultType 或 resultMap,但不能同时使用。

3.2.2 mybatis中的占位符

在上面的增删改查操作中,SQL语句中的值是写死在SQL语句中的,而在实际开发中,此处的值往往是用户提交过来的值,因此这里我们需要将SQL中写死的值替换为占位符。

在mybatis中占位符有两个,分别是 #{} 占位符 和 ${} 占位符:

  • #{}:相当于JDBC中的问号(?)占位符,是为SQL语句中的参数值进行占位,大部分情况下都是使用#{}占位符;并且当#{}占位符是为字符串或者日期类型的值进行占位时,在参数值传过来替换占位符的同时,会进行转义处理(在字符串或日期类型的值的两边加上单引号);
  • ${}:是为SQL片段(字符串)进行占位,将传过来的SQL片段直接拼接在 ${} 占位符所在的位置,不会进行任何的转义处理。(由于是直接将参数拼接在SQL语句中,因此可能会引发SQL注入攻击问题) 需要注意的是:使用 ${} 占位符为SQL语句中的片段占位时,即使只有一个占位符,需要传的也只有一个参数,也需要将参数先封装再传递!

3.2.3 动态SQL标签

if、where标签

  • 标签:是根据test属性中的布尔表达式的值,从而决定是否执行包含在其中的SQL片段。如果判断结果为true,则执行其中的SQL片段;如果结果为false,则不执行其中的SQL片段
  • 标签:用于对包含在其中的SQL片段进行检索,在需要时可以生成where关键字,并且在需要时会剔除多余的连接词(比如and或者or)

3.2. 4 foreach标签

foreach标签:可以对传过来的参数数组或集合进行遍历

3.3 Mybatis 增删改查

3.3.1 新增

编辑EmpMapper.xml

<!-- 练习2: 新增员工信息: 赵云 保安 6000
增删改的标签上不用指定resultType, 因为返回值都是int类型
-->
<update id="insert" >
    insert into emp value(null, '赵云', '保安', 6000)
</update>

实现和测试:

/*
新增员工信息: 赵云 保安 6000
 */
@Test
public void testInsert() throws IOException {
    //1.读取mybatis的核心配置文件(mybatis-config.xml)
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    //2.通过配置信息获取一个SqlSessionFactory工厂对象
    SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);
    //3.通过工厂获取一个SqlSession对象
    SqlSession session = fac.openSession();

    //执行sql语句, 返回执行结果
    int rows = session.update("EmpMapper.insert");
    //提交事务
    session.commit();
    System.out.println("影响的行数: "+rows);
}

3.3.2 修改

编辑EmpMapper.xml

<!-- 练习3:修改员工信息:赵云 保镖 20000 -->
<update id="update">
    update emp set job='保镖', salary=20000 where name='赵云'
</update>

实现和测试:

/*
练习3: 修改员工信息, 将赵云的job改为'保镖',salary改为20000
 */
@Test
public void testUpdate() throws IOException {
    //1.读取mybatis的核心配置文件(mybatis-config.xml)
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    //2.通过配置信息获取一个SqlSessionFactory工厂对象
    SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);
    //3.通过工厂获取一个SqlSession对象
    SqlSession session = fac.openSession();


    //执行sql语句, 返回执行结果
    int rows = session.update("EmpMapper.update");
    //提交事务
    session.commit();
    System.out.println("影响行数:"+rows);
}

3.3.3 删除

编辑EmpMapper.xml

<!-- 练习4: 删除name为'赵云'的记录 -->
<update id="delete">
    delete from emp where name='赵云'
</update>

实现和测试:

/*
练习4: 删除name为'赵云'的记录
 */
@Test
public void testDelete() throws IOException {
    //1.读取mybatis的核心配置文件(mybatis-config.xml)
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    //2.通过配置信息获取一个SqlSessionFactory工厂对象
    SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);
    //3.通过工厂获取一个SqlSession对象
    SqlSession session = fac.openSession();


    //执行sql语句, 返回执行结果
    int rows = session.update("EmpMapper.delete");
    //提交事务
    session.commit();
    System.out.println("影响行数:"+rows);
}

四、Mapper接口开发

Mapper接口开发介绍

在上面的Mybatis案例中, 通过SqlSession对象调用方法进行增删改查操作时,方法中需要传入的第一个参数是一个字符串值,该值对应的内容为: (Mapper文件中的)namespace + id, 通过这种方式,找到Mapper文件中映射的SQL语句并执行!!

这种方式由于传入的是字符串值, 很容易发生字符串拼写错误且编译时期不会提示。

这里我们将会讲解比上面更加简单的方式,也是我们企业开发中最常用的方式,即使用mapper接口开发。使用mapper接口开发需要注意以下几点:

  • 1、创建一个接口,接口的全限定类名和mapper文件的namespace值要相同
  • 2、mapper文件中每条要执行的SQL语句,在接口中要添加一个对应的方法,并且接口中的方法名和SQL标签上的id值相同
  • 3、Mapper接口中方法接收的参数类型,和mapper.xml中定义的sql的接收的参数类型要相同
  • 4、接口中方法的返回值类型和SQL标签上的resultType即返回值类型相同(如果方法返回值是集合,resultType只需要指定集合中的泛型)

Mapper接口开发实现

下面将使用mapper接口开发的方式,实现根据id查询指定的员工信息

1、创建Mapper接口(命名为EmpMapper2),并提供方法

public interface EmpMapper2 {

    /**
     * 根据id查询员工信息
     *
     * @param id
     * @return
     */
    public Emp findById(Integer id);
}

2、创建mapper.xml(EmpMapper2.xml),

注意:namespace要和mapper接口全限定名称相同

注意:id要和接口中的方法名相同

注意:返回类型要和接口中对应方法的返回类型相同

<?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.itest.mybatis.dao.EmpMapper2">
    <select id="findById" resultType="com.itest.mybatis.pojo.Emp">
        select * from emp where id = #{id}
    </select>
</mapper>

新创建的mapper.xml需要加入到mybatis-config.xml中mappers标签中

<mappers>
    <!-- 配置MyBatis需要加载的Mapper -->
    <mapper resource="EmpMapper.xml"/>
    <mapper resource="EmpMapper2.xml"/>
</mappers>

3、提供实现类,测试Emp接口中的根据id查询员工的方法

public class TestMybatisInf {

    @Test
    public void testFindById() throws Exception{
        //1.读取mybatis的核心配置文件(mybatis-config.xml)
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

        //2.通过配置信息获取一个SqlSessionFactory工厂对象
        SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);

        //3.通过工厂获取一个SqlSession对象
        SqlSession session = fac.openSession();

        //4.获取Mapper接口对象
        EmpMapper2 map = session.getMapper(EmpMapper2.class);

        //5.调用接口对象的方法进行查询
        Emp e = map.findById(2);

        //6.输出结果
        System.out.println(e);
    }
}

参考资料:

https://mybatis.org/mybatis-3/zh/getting-started.html


Similar Posts

上一篇 First Flutter APP

Comments