0%

mybatis源码一(SqlSessionFactory)

一:首先

对MyBatis的使用我们在最开始都已经知道可以通过xml配置文件的方式,也可以通过Java代码创建Configuration对象的方式。 这两者实际上是一样,xml配置文件的方式最终也是通过解析xml配置文件创建一个Configuration对象。可能对于很多人(我也是)来说MyBatis通常是和Spring配合使用,用了N年MyBatis也不能把MyBatis说个所以出来

回顾一下mybatis实例的创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一步,创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 第二步,加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 第三步,创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 第四步,创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第五步,使用SqlSession对象执行查询,得到User对象
// 第一个参数:执行查询的StatementId
User user = sqlSession.selectOne("getUserById", 10);
// 第六步,打印结果
System.out.println(user);
// 第七步,释放资源,每一个sqlSession就是一个连接
sqlSession.close();

二:创建 SqlSessionFactory

在创建一个SqlSession实例时,首先需要创建一个SqlSessionFactory实例,而又需要通过SqlSessionFactoryBuilder()build()来创建SqlSessionFactory

先看SqlSessionFactoryBuilder这个类,放在package org.apache.ibatis.session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class SqlSessionFactoryBuilder {

//--------------------------------通过读取字符流(Reader)的方式构件SqlSessionFactory-----------------
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.
}
}
}


//-----------------------通过读取字符流(Reader)的方式构件SqlSessionFactory---------------
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.
}
}
}
//--------------------------------创建SqlSessionFactory--------------------------
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
  • 第1,2种方式是通过配置文件方式,第3种是通过Java代码方式。

三.是如何创建SqlSessionFactory的

以通过InputStream字节流的方式来看,和它相关的一共有4个构造方法,其中第2个和第3个参数并不陌生

1
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)

这相当于在告诉这两个配置项environment,properties是可以通过在构建SqlSessionFactory的时候进行配置的或重新配置(此时优先级最高)。XMLConfigBuilder工具类对配置文件进行解析成Configuration对象,

1
2
3
4
5
//参考上面的代码
public SqlSessionFactory build(){
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
}

再调用构建出SqlSessionFactory,构建出SqlSessionFactory

四 所以

兜兜转转,不管是配置文件还是Java代码,最后都会经过解析通过Configuration对象产生SqlSessionFactory,也就是最后一个build(Configuration config)

然而看最后一个方法的时候,返回的不是SqlSessionFactory,而是DefaultSqlSessionFactory实例,那是因为实际上SqlSessionFactory是一个接口,而DefaultSqlSessionFactory是它的实现类.暂且不管SqlSessionManager,暂时只需知道SqlSessionFactoryDefaultSqlSessionFactorySqlSessionManager

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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”来构造的
  • 还有事务的隔离级别等等

至于DefaultSqlSessionFactory对SqlSessionFactory的具体实现,除了以上方法之外,还包括了:openSessionFromDataSource、openSessionFromConnection、getTransactionFactoryFromEnvironment、closeTransaction。到这里我们似乎还是只停留在表面,并没有涉及相对比较底层的代码啊

我们这是刚走了一遍SqlSession创建过程的流程。

五: 下面我们从return new DefaultSqlSessionFactory(config)开始。

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

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

1
private final Configuration configuration;

为什么会使用final关键字对Configuration对象进行修饰呢?

  • Configuration应该是存在于MyBatis的整个生命周期那么意味着它应该是有且仅有一个实例的,而final关键字修饰的变量字段就代表它是不可变对象
  • 这也恰好能解释说明官方所说的SqlSessionFactory应该是单例的。

首先,MyBatis认为配置文件之所以是配置文件,那么就以为着它只有一种配置,就好比我们将一个新手机买回来过后,设置时间、日期就不再去更改,但我们可能会出国,这个时候就要配置选用另一个时区的时间,不过我还是使用的是这个手机的设置,换句话说,你的手机不可能有两个系统设置吧。

所以Configuration对象实际上就是我们手机上的系统设置。而SqlSessionFactory是通过Configuration来构造SqlSession的,对Configuration的引用当然是不可变的,如果可变,那相当于你手机里岂不是可以新建一个系统设置?那不就乱套了?索性final,对象不可变。

此时也就建议SqlSessionFactory是单例的了,你构建N个SqlSessionFactory,它们也是通过一个Configuration对象来构造的SqlSession实例,那还有必要有N个SqlSessionFactory了吗?显然没有必要,所以最好就是将SqlSessionFactory设计为单例。

这才对DefaultSqlSessionFactory类第一句话进行了解读,接着就是实现SqlSessionFactory接口的8个构造方法。DefaultSqlSessionFactory并没有直接实现这8个构造方法而是调用另外两个新的方法.

这8个构造方法实际上分为两大类:

  • 一个是从数据源中获取SqlSession.
  • 一个是从Connection中获取SqlSession(包含Connection参数的那两个构造函数)。

先看从数据源中获取SqlSession。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的构建。而这也是最关键最重要最发杂的一步

参考:https://www.cnblogs.com/yulinfeng/p/6063974.html