MyBatis源码解读(1)——SqlSessionFactory
在前面对 MyBatis 稍微有点了解过后,现在来对 MyBatis 的源码试着解读一下,并不是解析,暂时定为解读。所有对 MyBatis 解读均是基于 MyBatis-3.4.1,官网中文文档:http://www.mybatis.org/mybatis-3/zh/getting-started.html,MyBatis-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 包中。
1 public class SqlSessionFactoryBuilder { 2 3 public SqlSessionFactory build(Reader reader) { 4 return build(reader, null, null); 5 } 6 7 public SqlSessionFactory build(Reader reader, String environment) { 8 return build(reader, environment, null); 9 } 10 11 public SqlSessionFactory build(Reader reader, Properties properties) { 12 return build(reader, null, properties); 13 } 14 15 public SqlSessionFactory build(Reader reader, String environment, Properties properties) { 16 try { 17 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); 18 return build(parser.parse()); 19 } catch (Exception e) { 20 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 21 } finally { 22 ErrorContext.instance().reset(); 23 try { 24 reader.close(); 25 } catch (IOException e) { 26 // Intentionally ignore. Prefer previous error. 27 } 28 } 29 } 30 31 public SqlSessionFactory build(InputStream inputStream) { 32 return build(inputStream, null, null); 33 } 34 35 public SqlSessionFactory build(InputStream inputStream, String environment) { 36 return build(inputStream, environment, null); 37 } 38 39 public SqlSessionFactory build(InputStream inputStream, Properties properties) { 40 return build(inputStream, null, properties); 41 } 42 43 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 44 try { 45 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 46 return build(parser.parse()); 47 } catch (Exception e) { 48 throw ExceptionFactory.wrapException("Error building SqlSession.", e); 49 } finally { 50 ErrorContext.instance().reset(); 51 try { 52 inputStream.close(); 53 } catch (IOException e) { 54 // Intentionally ignore. Prefer previous error. 55 } 56 } 57 } 58 59 public SqlSessionFactory build(Configuration config) { 60 return new DefaultSqlSessionFactory(config); 61 } 62 63 }
我们可以看到这个类用很多的构造方法,但主要分为三大类: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 实例的方法。
1 public interface SqlSessionFactory { 2 3 SqlSession openSession(); 4 5 SqlSession openSession(boolean autoCommit); 6 SqlSession openSession(Connection connection); 7 SqlSession openSession(TransactionIsolationLevel level); 8 9 SqlSession openSession(ExecutorType execType); 10 SqlSession openSession(ExecutorType execType, boolean autoCommit); 11 SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); 12 SqlSession openSession(ExecutorType execType, Connection connection); 13 14 Configuration getConfiguration(); 15 16 }
这么多的 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。
1 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 2 Transaction tx = null; 3 try { 4 final Environment environment = configuration.getEnvironment(); 5 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); 6 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); 7 final Executor executor = configuration.newExecutor(tx, execType); 8 return new DefaultSqlSession(configuration, executor, autoCommit); 9 } catch (Exception e) { 10 closeTransaction(tx); // may have fetched a connection so lets call close() 11 throw ExceptionFactory.wrapException("Error opening session. Cause:" + e, e); 12 } finally { 13 ErrorContext.instance().reset(); 14 } 15 }
如果没有传入 ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit 这三个参数就代表使用我们 Configuration 对象中的配置(看来 Executor、TransactionIsolationLevel、autoCommit 是可以灵活配置的)。第 8 行创建出一个 DefaultSqlSession 实例,可以猜测 SqlSession 是一个接口而 DefaultSqlSession 是其实现类。对于 SqlSession 的创建过程,我们马上就要走到最后一步 SqlSession 的构建。而这也是最关键最重要最发杂的一步。