在前面对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的构建。而这也是最关键最重要最发杂的一步。