在前面对MyBatis稍微有点了解过后,现在来对MyBatis的源码试着解读一下,并不是解析,暂时定为解读。所有对MyBatis解读均是基于MyBatis-3.4.1,官网中文文档:http://www.mybatis.org/mybatis-3/zh/getting-started.htmlMyBatis-3.4.1.jar

本应在开始读MyBatis源码时首先应该了解下MyBatis的SqlSession的四大对象:Executor、StatemenHandler、ParameterHandler、ResultHandler,但我想把这四大对象放到我们源码中一步一步来解读。

开始。


对MyBatis的使用我们在最开始都已经知道可以通过xml配置文件的方式,也可以通过Java代码创建Configuration对象的方式。这两者实际上是一样,xml配置文件的方式最终也是通过解析xml配置文件创建一个Configuration对象。可能对于很多人来说MyBatis通常是和Spring配合使用,用了N年MyBatis也不能把MyBatis说个所以出来。写MyBatis的这个系列,正式希望不要只光会用,还要懂其原理,熟悉一个语言、一个框架的特性原理才能在不同场合使用不同的特性。

回到正题,我们说到使用MyBatis第一步就是配置,或者说第一个重要的对象就是Configuration。但我想要阅读的第一个源码并不是Configuration类,我们暂且知道它会贯穿整个MyBatis的生命周期,它是存放一些配置所在的地方即可。我们要说的是MyBatis第二个重要部分——SqlSession的执行过程。

从官方文档的说明,我们可以知道SqlSession是由SqlSessionFactory创建,SqlSessionFactoryBuilder创建。

我们通过代码简单回顾一下SQLSession实例的创建过程。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

在创建一个SqlSession实例时,首先需要创建一个SqlSessionFactory实例,而又需要通过SqlSessionFactoryBuilder()的build静态方法来创建SqlSessionFactory。(关于这三者的作用域(Scope)及生命周期之前有介绍过,这里不再多讲,参考《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》

所以我们首先来看看SqlSessionFactoryBuilder这个类。它放置在package org.apache.ibatis.session包中。

 public class SqlSessionFactoryBuilder {

   public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
} public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
} public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
} public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
} public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
} public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
} public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
} public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
} public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
} }

我们可以看到这个类用很多的构造方法,但主要分为三大类:1、第3-29行是通过读取字符流(Reader)的方式构件SqlSessionFactory。2、第31-57行是通过字节流(InputStream)的方式构件SqlSessionFacotry。3、第59行直接通过Configuration对象构建SqlSessionFactory。第1、2种方式是通过配置文件方式,第3种是通过Java代码方式。

让我们再仔细来看到底是怎么构建出SqlSessionFactory的呢?以通过InputStream字节流的方式来看,和它相关的一共有4个构造方法,其中最后一个public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties),第2个和第3个参数并不陌生,这相当于在告诉这两个配置项environment、properties是可以通过在构建SqlSessionFactory的时候进行配置的或重新配置(此时优先级最高)。首先通过第45-46行代码XMLConfigBuilder工具类对配置文件进行解析成Configuration对象,再调用public SqlSessionFactory build(Configuration config)构建出SqlSessionFactory,所以兜兜转转,不管是配置文件还是Java代码,最后都会经过解析通过Configuration对象产生SqlSessionFactory。

我们可以发现第60行代码返回的是DefaultSqlSessionFactory实例,而不是SqlSessionFactory。那是因为实际上SqlSessionFactory是一个接口,而DefaultSqlSessionFactory是它的实现类。如下图所示。

在这里我们暂且不管SqlSessionManager,我们只需知道SqlSessionFactory有DefaultSqlSessionFactory和SqlSessionManager。在SqlSessionFactory可以猜测一下有什么方法。

回顾SqlSession的创建过程,其实我们也能猜测得到SqlSessionFactory一定主要是创建SqlSession实例的方法。

 public interface SqlSessionFactory {

   SqlSession openSession();

   SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }

这么多的openSession重载方法,都是通过传入不同的参数构造SqlSession实例,有通过设置事务是否自动提交"autoCommit",有设置执行器类型"ExecutorType"来构造的,还有事务的隔离级别等等。最后一个方法就告诉我们可以通过SqlSessionFactory来获取Configuration对象。至于DefaultSqlSessionFactory对SqlSessionFactory的具体实现,除了以上方法之外,还包括了:openSessionFromDataSource、openSessionFromConnection、getTransactionFactoryFromEnvironment、closeTransaction。到这里我们似乎还是只停留在表面,并没有涉及相对比较底层的代码啊,别急。我们这是刚走了一遍“SqlSession创建过程”的流程。下面我们从SqlSessionFactoryBuilder第60行return new DefaultSqlSessionFactory(config)开始。


由于SqlSessionFactory的实现类DefaultSqlSessionFactory,源码过长,我们在其中以截取关键的代码作为解读。

DefaultSqlSessionFactory中的第1行代码实际上就非常值得我们思考:final关键字。

private final Configuration configuration;

为什么会使用final关键字对Configuration对象进行修饰呢?Configuration应该是存在于MyBatis的整个生命周期那么意味着它应该是有且仅有一个实例的,而final关键字修饰的变量字段就代表它是不可变对象《“不可变的对象”与“不可变的对象引用”》),这也恰好能解释说明官方User Guide中所说的SqlSessionFactory应该是单例的。但这是设计在前?还是规则在前呢?如果是设计在前,那为什么这样设计?如果是规则在前,是什么样的规则规定了这样做呢?我认为是设计在前。

首先,MyBatis认为配置文件之所以是配置文件那么就以为着它只有一种配置(这个说法并不是很全面,因为我们已经见到了那么多的构造方法就说明在一个应用程序中可以通过不同的场景配置选用不同的配置,事实也如此),就好比我们将一个新手机买回来过后,设置时间、日期就不再去更改,但我们可能会出国,这个时候就要配置选用另一个时区的时间,不过我还是使用的是这个手机的设置,换句话说,你的手机不可能有两个系统设置吧。所以Configuration对象实际上就是我们手机上的系统设置。而SqlSessionFactory是通过Configuration来构造SqlSession的,对Configuration的引用当然是不可变的,如果可变,那相当于你手机里岂不是可以新建一个系统设置?那不就乱套了?索性final,对象不可变。此时也就建议SqlSessionFactory是单例的了,你构建N个SqlSessionFactory,它们也是通过一个Configuration对象来构造的SqlSession实例,那还有必要有N个SqlSessionFactory了吗?显然没有必要,所以最好就是将SqlSessionFactory设计为单例。同样可参考《SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession作用域(Scope)和生命周期》

这才对DefaultSqlSessionFactory类第一句话进行了解读,接着就是实现SqlSessionFactory接口的8个构造方法。DefaultSqlSessionFactory并没有直接实现这8个构造方法而是调用另外两个新的方法,这8个构造方法实际上分为两大类:一个是从数据源中获取SqlSession,一个是从Connection中获取SqlSession(包含Connection参数的那两个构造函数)。

先看从数据源中获取SqlSession。

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

如果没有传入ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit这三个参数就代表使用我们Configuration对象中的配置(看来Executor、TransactionIsolationLevel、autoCommit是可以灵活配置的)。第8行创建出一个DefaultSqlSession实例,可以猜测SqlSession是一个接口而DefaultSqlSession是其实现类。对于SqlSession的创建过程,我们马上就要走到最后一步SqlSession的构建。而这也是最关键最重要最发杂的一步。

MyBatis源码解读(1)——SqlSessionFactory的更多相关文章

  1. Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

    本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...

  2. MyBatis源码解读(3)——MapperMethod

    在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在 ...

  3. MyBatis源码解读之延迟加载

    1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当 ...

  4. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  5. Mybatis源码解读-插件

    插件允许对Mybatis的四大对象(Executor.ParameterHandler.ResultSetHandler.StatementHandler)进行拦截 问题 Mybatis插件的注册顺序 ...

  6. 【转】Mybatis源码解读-设计模式总结

    原文:http://www.crazyant.net/2022.html?jqbmtw=b90da1&gsjulo=kpzaa1 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开 ...

  7. Mybatis源码解读-设计模式总结

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  8. mybatis源码分析(1)——SqlSessionFactory实例的产生过程

    在使用mybatis框架时,第一步就需要产生SqlSessionFactory类的实例(相当于是产生连接池),通过调用SqlSessionFactoryBuilder类的实例的build方法来完成.下 ...

  9. mybatis源码解读(一)——初始化环境

    本系列博客将对mybatis的源码进行解读,关于mybatis的使用教程,可以查看我前面写的博客——传送门. 为了便于后面的讲解,我们这里首先构造一个统一环境.也可以参考mybatis官网. 1.数据 ...

随机推荐

  1. taobao-pamirs-proxycache开源缓存代理框架实现原理剖析

    写在前面 taobao-pamirs-proxycache 是一款开源缓存代理框架, 它将 缓存代码 与 业务代码 解耦.让开发专注coding业务, 缓存通过xml配置即可实现.本文先从此工具如何使 ...

  2. Visual Studio For MacOS .NetCore开发踩坑记

    自从Visual Studio For  MacOS公布以来,就开始尝试在Mac上进行net core开发.断断续续遇到了各种奇奇怪怪的问题.虽然大部分利用google查查(百度屁都查不出来),都能找 ...

  3. Python之路-正则表达式

    作业一:整理正则表达式博客 正则表通常被用来检索.替换那些符合某个模式(规则)的文本,为了提取对自己有用的信息,由命令解释执行:而通配符和命令是同一级别,为了提示处理效率,直接由shell解释执行. ...

  4. 老李分享:loadrunne动态查询db2数据库

    老李分享:loadrunne动态查询db2数据库   poptest老李认为性能测试脚本开发不仅仅涉及到脚本开发的技术层面,同时也要对一些其他技术有所了解动态链接库的技术,线程安全等等,建议在做下面的 ...

  5. AngularJS1.X学习笔记4-内置事件指令及其他

    AngularJS为我们定义了一系列事件指令,方便我们对用户的操作作出响应.甚至他还有一个可选模块提供了触摸事件和手势事件的支持,为移动端开发提供了可能.现在开始学习一下AngularJS的事件指令. ...

  6. fixed应用

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  7. day002-HTML知识点总结:浏览器兼容性之指定IE浏览器使用chrome内核渲染页面

    今天再浏览大淘宝首页时,突然看到这么一个东东: ,顿时好费解,莫非万恶的IE浏览器认识到自己以往的罪孽,开始兼容chrome了??! 于是本着不懂就百度的神精,开始纵横于各大铁耙,勃哥,终于找到了许许 ...

  8. Python学习_argsparse

    # -*- coding: utf-8 -*- import argparse args = "-f hello.txt -n 1 2 3 -x 100 -y b -z a -q hello ...

  9. 关于css禁止文本复制属性

    最近在做DHTMLX框架替换,新框架dhx的grid是不能选中内容复制的 虽然相对来说是安全些的,但是客户体验度一定会大打折扣 网页上禁止复制主要靠JavaScript来实现.<BODY onc ...

  10. Hadoop之HDFS原理及文件上传下载源码分析(下)

    上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文 ...