MyBatis

什么是 MyBatis

MyBatis 是一种半自动化的 Java 持久层框架,其通过注解或 XML 的方式将对象和 SQL 关联起来。之所以说它是半自动的,是因为和 Hibernate 等一些可自动生成 SQL 的 ORM(Object Relational Mapping) 框架相比,使用 MyBatis 需要用户自行维护 SQL。维护 SQL 的工作比较繁琐,但也有好处。比如我们可控制 SQL 逻辑,可对其进行优化,以提高效率。

MyBatis工作原理

 

mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件(也可以用Java文件配置的方式,需要添加@Configuration)来构建SqlSessionFactory(SqlSessionFactory是线程安全的);

然后,SqlSessionFactory的实例直接开启一个SqlSession再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。

说明:SqlSession是单线程对象,因为它是非线程安全的,是持久化操作的独享对象,类似jdbc中的Connection,底层就封装了jdbc连接

详细流程如下:

1、加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着<select | update | delete | insert>标签项。

2、SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。 SqlSession 是通过 JDK 动态代理的方式为接口生成代理对象的。

3、SqlSession对象完成和数据库的交互:
a、调用户程序用mybatis接口层api(即Mapper接口中的方法),传入参数:为SQL的ID和传入参数对象
b、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象
c、通过Executor(负责动态SQL的生成和查询缓存的维护,execute 方法主要由一个 switch 语句组成,用于根据 SQL 类型执行相应的数据库操作。)将MappedStatement对象进行解析,解析后可以得到最终要执行的SQL语句和参数。sql参数转化、动态sql拼接,生成jdbc Statement对象,
d、JDBC执行sql。

e、借助MappedStatement中的结果映射关系,将返回结果转化成HashMap、JavaBean等存储结构并返回。

 

我们把Mybatis的功能架构分为三层:

(1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

(2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

 

为什么要使用 MyBatis

我们在使用 Java 程序访问数据库时,有多种选择。比如我们可通过编写最原始的 JDBC 代码访问数据库,或是通过 Spring 提供的 JdbcTemplate 访问数据库。除此之外,我们还可以选择 Hibernate,或者本篇的主角 MyBatis 等。在有多个可选项的情况下,我们为什么选择 MyBatis 呢?

public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory;

    
    public void prepare() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
    }

    
    public void testMyBatis() throws IOException {
        SqlSession session = sqlSessionFactory.openSession();
        try {
            ArticleDao articleDao = session.getMapper(ArticleDao.class);
            List<Article> articles = articleDao.findByAuthorAndCreateTime("coolblog.xyz", "2018-06-10");
        } finally {
            session.commit();
            session.close();
        }
    }
}

在上面的测试代码中,prepare 方法用于创建SqlSessionFactory工厂,该工厂的用途是创建SqlSession。通过 SqlSession,可为我们的数据库访问接口ArticleDao接口生成一个代理对象。MyBatis 会将接口方法findByAuthorAndCreateTime和 SQL 映射文件中配置的 SQL 关联起来,这样调用该方法等同于执行相关的 SQL。

 使用 JDBC 访问数据库

这种方式的代码流程一般是加载数据库驱动,创建数据库连接对象,创建 SQL 执行语句对象,执行 SQL 和处理结果集等。

上面代码的步骤比较多,但核心步骤只有两部,分别是执行 SQL 和处理查询结果。从开发人员的角度来说,我们也只关心这两个步骤。如果每次为了执行某个 SQL 都要写很多额外的代码。比如打开驱动,创建数据库连接,就显得很繁琐了。当然我们可以将这些额外的步骤封装起来,这样每次调用封装好的方法即可。这样确实可以解决代码繁琐,冗余的问题。不过,使用 JDBC 并非仅会导致代码繁琐,冗余的问题。在上面的代码中,我们通过字符串对 SQL 进行拼接。这样做会导致两个问题,第一是拼接 SQL 可能会导致 SQL 出错,比如少了个逗号或者多了个单引号等。第二是将 SQL 写在代码中,如果要改动 SQL,就需要到代码中进行更改。这样做是不合适的,因为改动 Java 代码就需要重新编译 Java 文件,然后再打包发布。同时,将 SQL 和 Java 代码混在一起,会降低代码的可读性,不利于维护。关于拼接 SQL,是有相应的处理方法。比如可以使用 PreparedStatement,同时还可解决 SQL 注入的问题。

除了上面所说的问题,直接使用 JDBC 访问数据库还会有什么问题呢?这次我们将目光转移到执行结果的处理逻辑上。从上面的代码中可以看出,我们需要手动从 ResultSet 中取出数据,然后再设置到 Article 对象中。假如 Article 对象有几十个属性,再用上面的方式接收查询结果,会非常的麻烦。而且可能还会因为属性太多,导致忘记设置某些属性。以上的代码还有一个问题,用户需要自行处理受检异常,这也是导致代码繁琐的一个原因。哦,还有一个问题,差点忘了。用户还需要手动管理数据库连接,开始要手动获取数据库连接。使用好后,又要手动关闭数据库连接。不得不说,真麻烦。

没想到直接使用 JDBC 访问数据库会有这么多的问题。如果在生产环境直接使用 JDBC,怕是要被 Leader 打死了。当然,视情况而定。如果项目非常小,且对数据库依赖比较低。直接使用 JDBC 也很方便,不用像 MyBatis 那样搞一堆配置了。

MyBatis VS JDBC

与 JDBC 相比,MyBatis 缺点比较明显,它的配置比较多,特别是 SQL 映射文件。如果一个大型项目中有几十上百个 Dao 接口,就需要有同等数量的 SQL 映射文件,这些映射文件需要用户自行维护。不过与 JDBC 相比,维护映射文件不是什么问题。不然如果把同等数量的 SQL 像 JDBC 那样写在代码中,那维护的代价才叫大,搞不好还会翻车。除了配置文件的问题,大家会发现使用 MyBatis 访问数据库好像过程也很繁琐啊。它的步骤大致如下:

  1. 读取配置文件
  2. 创建 SqlSessionFactoryBuilder 对象
  3. 通过 SqlSessionFactoryBuilder 对象创建 SqlSessionFactory
  4. 通过 SqlSessionFactory 创建 SqlSession
  5. 为 Dao 接口生成代理类
  6. 调用接口方法访问数据库

如上,如果每次执行一个 SQL 要经过上面几步,那和 JDBC 比较起来,也没什优势了。不过这里大家需要注意,SqlSessionFactoryBuilder 和 SqlSessionFactory 以及 SqlSession 等对象的作用域和生命周期是不一样的,这一点在 MyBatis 官方文档中说的比较清楚,我这里照搬一下。SqlSessionFactoryBuilder 对象用于构建 SqlSessionFactory,只要构建好,这个对象就可以丢弃了。SqlSessionFactory 是一个工厂类,一旦被创建就应该在应用运行期间一直存在,不应该丢弃或重建。SqlSession 不是线程安全的,所以不应被多线程共享。官方推荐的使用方式是有按需创建,用完即销毁。因此,以上步骤中,第1、2和第3步只需执行一次。第4和第5步需要进行多次创建。至于第6步,这一步是必须的。所以比较下来,MyBatis 的使用方式还是比 JDBC 简单的。同时,使用 MyBatis 无需处理受检异常,比如 SQLException。另外,把 SQL 写在配置文件中,进行集中管理,利于维护。同时将 SQL 从代码中剥离,在提高代码的可读性的同时,也避免拼接 SQL 可能会导致的错误。除了上面所说这些,MyBatis 会将查询结果转为相应的对象,无需用户自行处理 ResultSet。

总的来说,MyBatis 在易用性上要比 JDBC 好太多。不过这里拿 MyBatis 和 JDBC 进行对比并不太合适。JDBC 作为 Java 平台的数据库访问规范,它仅提供一种访问数据库的能力。至于使用者觉得 JDBC 流程繁琐,还要自行处理异常等问题,这些还真不怪 JDBC。比如 SQLException 这个异常,JDBC 没法处理啊,抛给调用者处理也是理所应当的。至于繁杂的步骤,这仅是从使用者的角度考虑的,从 JDBC 的角度来说,这里的每个步骤对于完成一个数据访问请求来说都是必须的。至于 MyBatis,它是构建在 JDBC 技术之上的,对访问数据库的操作进行了简化,方便用户使用。综上所述,JDBC 可看做是一种基础服务,MyBatis 则是构建在基础服务之上的框架,它们的目标是不同的。

使用 Spring JDBC 访问数据库

Spring JDBC 在 JDBC 基础上,进行了比较薄的包装,易用性得到了不少提升。那下面我们来看看如何使用 Spring JDBC。

我们在使用 Spring JDBC 之前,需要进行一些配置。这里我把配置信息放在了 application.xml 文件中,后面写测试代码时,让容器去加载这个配置。

Spring JDBC 还是比较容易使用的。不过它也是存在一定缺陷的,比如 SQL 仍是写在代码中。又比如,对于较为复杂的结果(数据库返回的记录包含多列数据),需要用户自行处理 ResultSet 等。不过与 JDBC 相比,使用 Spring JDBC 无需手动加载数据库驱动,获取数据库连接,以及创建 Statement 对象等操作。总的来说,易用性上得到了不少的提升。

 MyBatis VS Hibernate

从映射关系上来说,Hibernate 是把实体类(POJO)和表进行了关联,是一种完整的 ORM (O/R mapping) 框架。而 MyBatis 则是将数据访问接口(Dao)与 SQL 进行了关联,本质上算是一种 SQL 映射。从使用的角度来说,使用 Hibernate 通常不需要写 SQL,让框架自己生成就可以了。但 MyBatis 则不行,再简单的数据库访问操作都需要有与之对应的 SQL。另一方面,由于 Hibernate 可自动生成 SQL,所以进行数据库移植时,代价要小一点。而由于使用 MyBatis 需要手写 SQL,不同的数据库在 SQL 上存在着一定的差异。这就导致进行数据库移植时,可能需要更改 SQL 的情况。不过好在移植数据库的情况很少见,可以忽略。

Hibernate 可自动生成 SQL,降低使用成本。但同时也要意识到,这样做也是有代价的,会损失灵活性。比如,如果我们需要手动优化 SQL,我们很难改变 Hibernate 生成的 SQL。因此对于 Hibernate 来说,它适用于一些需求比较稳定,变化比较小的项目,譬如 OA、CRM 等。

与 Hibernate 相反,MyBatis 需要手动维护 SQL,这会增加使用成本。但同时,使用者可灵活控制 SQL 的行为,这为改动和优化 SQL 提供了可能。所以 MyBatis 适合应用在一些需要快速迭代,需求变化大的项目中,这也就是为什么 MyBatis 在互联网公司中使用的比较广泛的原因。除此之外,MyBatis 还提供了插件机制,使用者可以按需定制插件。这也是 MyBatis 灵活性的一个体现。

在 Spring 中使用

在实际开发中,我们一般都会将 MyBatis 和 Spring 整合在一起使用。这样,我们就可以通过 bean 注入的方式使用各种 Dao 接口。MyBatis 和 Spring 原本是两个完全不相关的框架,要想把两者整合起来,需要一个中间框架。这个框架一方面负责加载和解析 MyBatis 相关配置。另一方面,该框架还会通过 Spring 提供的拓展点,把各种 Dao 接口及其对应的对象放入 bean 工厂中。这样,我们才可以通过 bean 注入的方式获取到这些 Dao 接口对应的 bean。那么问题来了,具有如此能力的框架是谁呢?答案是mybatis-spring

除了配置sqlsessionFactory,还需要配置 MapperScannerConfigurer,这个类顾名思义,用于扫描某个包下的数据访问接口,并将这些接口注册到 Spring 容器中。这样,我们就可以在其他的 bean 中注入 Dao 接口的实现类,无需再从 SqlSession 中获取接口实现类。

Mybatis 一级缓存和二级缓存

一级缓存
Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Mabits默认开启一级缓存。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行SQL时候两次查询中间发生了增删改的操作,则SQLSession的缓存会被清空。 每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。 Mybatis的内部缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。 SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。

一级缓存的生命周期:
a. MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

b. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;

c. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;

d.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
 

二级缓存
二级缓存 二级缓存是mapper级别的,Mybatis默认是没有开启二级缓存的。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值