MyBatis 源码阅读笔记(一)

1. MyBatis 简介

MyBatis 的官方网站有中文版本,它是这么介绍自己的:

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

MyBatis 的文档极短,源码相对容易阅读,GitHub 上也有中文注释的源码。我将尝试综合 MyBatis 的文档、中文注释的源码对 MyBatis 源码进行阅读。

2. 参考 GitHub Repositories

MyBatis 中文注释的源码:mybatis
MyBatis 教程示例:MyBatisTutorial

3. 测试代码

下面是一段简单的 MyBatis 测试代码:

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
package com.heyuhuan.dao;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

/**
* 博客 DAO 测试类
*
* @author 何宇寰
* @create 2017-03-07-11:38
*/
public class BlogDaoTest {

SqlSessionFactory sqlSessionFactory;

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

@Test
public void insertBlog() {
SqlSession session = sqlSessionFactory.openSession();
try {
BlogDao blogDao = session.getMapper(BlogDao.class);
blogDao.insertBlog("2");
session.commit();
} finally {
session.close();
}
}

}

代码的执行过程:

  1. 通过 Resources 类的 getResourceAsStream 方法,以 XML 文件名获取 InputStream;
  2. 通过 SqlSessionFactoryBuilder 的 build 方法以 InputStream 获取 SqlSessionFactory;
  3. 通过 SqlSessionFactory 的 openSession 方法获取 SqlSession;
  4. 通过 SqlSession 的 getMapper 方法以 DAO 接口获取 DAO 的实现类;
  5. 通过 SqlSession 的 commit 方法提交事务;
  6. 通过 SqlSession 的 close 方法关闭 session。

本篇笔记将记录前两步的源码阅读过程。

4. 源码阅读

4.1 获取 InputStream

通过 ClassLoader 的 getResourceAsStream 方法可以返回读取指定资源的输入流,指定的资源位于当前路径或者 classpath 的路径下。Resources 类并不直接操作 ClassLoader,而是通过 ClassLoaderWrapper 类操作 ClassLoader。ClassLoaderWrapper 会从五个 ClassLoader 尝试获取资源。这五个 ClassLoader 分别是:作为参数传入的(classLoader)、默认的(defaultClassLoader)、当前线程的(Thread.currentThread().getContextClassLoader())、类对象的(getClass().getClassLoader())和应用程序类加载器(systemClassLoader)。在尝试获取资源时,分别会以相对路径和绝对路径进行获取,一旦获取到立即返回。ClassLoaderWrapper 的尝试获取资源的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {

// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);

// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}

if (null != returnValue) {
return returnValue;
}
}
}
return null;
}

4.2 获取 SqlSessionFactory

获取 SqlSessionFactory 的过程比较复杂,本篇笔记涉及到的类的类图如下:

MyBatis 中,很多重要的对象都是通过建造者模式获取的,比如 SqlSessionFactory 就是通过 SqlSessionFactoryBuilder 获取的。SqlSessionFactoryBuilder 的 build 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}

可以看出,build 方法的关键在于通过 XMLConfigBuilder 获取 Configuration,进而通过 Configuration 获取 SqlSessionFactory。Configuration 对象包含了大部分的配置项,包含了众多的注册机和众多的集合,提供了大量的重要方法,是 MyBatis 中最重要的对象之一。Configuration 是一个重量级对象,一般一个数据源对应一个 Configuration 对象。

通过 XMLConfigBuilder 获取 Configuration 主要执行了两个方法:

  • XMLConfigBuilder 的构造方法;
  • XMLConfigBuilder 的 parse 方法。

4.2.1 XMLConfigBuilder 的构造方法

XMLConfigBuilder 的构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private boolean parsed;
private XPathParser parser;
private String environment;

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}

XMLConfigBuilder 的父类是 BaseBuilder,其构造方法如下:

1
2
3
4
5
6
7
8
9
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;

public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

可以看出,XMLConfigBuilder 的构造方法的主要有两个步骤:

  • 以 InputStream 获取 XPathParser,进而更容易地读取 XML 文件。这个步骤主要是封装了 JDK 的类包,比较简单,不再赘述。
  • 初始化 Configuration 对象,记录 Configuration 对象。

初始化 Configuration 对象的过程包括 Configuration 对象变量的初始化和执行 Configuration 对象的构造方法。Configuration 对象变量的初始化如下:

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
// 环境
protected Environment environment;

// 以下都是 <settings> 节点
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
// 默认启用缓存
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
// 默认为简单执行器
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 以上都是 <settings> 节点

protected Properties variables = new Properties();
// 对象工厂和对象包装器工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 映射注册机
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
// 默认禁用延迟加载
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory();

protected String databaseId;
protected Class<?> configurationFactory;

protected final InterceptorChain interceptorChain = new InterceptorChain();
// 类型处理器注册机
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 类型别名注册机
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

// 映射的语句,存在 Map 里
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
// 缓存,存在 Map 里
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
// 结果映射,存在 Map 里
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

// 不完整的 SQL 语句
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

protected final Map<String, String> cacheRefMap = new HashMap<String, String>();

可以看出,Configuration 对象的变量与 MyBatis 核心配置文件是对应的。进而可以推测,XMLConfigBuilder 的 parse 方法就是通过 XML 文件对 Configuration 对象的变量进行进一步的设置。

Configuration 对象的构造方法如下:

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
public Configuration(Environment environment) {
this();
this.environment = environment;
}

public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

可以看出,Configuration 对象的构造方法主要是对 typeAliasRegistry 注册更多的别名,对 languageRegistry 注册默认的 XML 语言驱动。这些注册机的作用在之后用到的时候再做分析。

4.2.2 XMLConfigBuilder 的 parse 方法

XMLConfigBuilder 的 parse 方法如下:

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
// 解析配置
public Configuration parse() {
// 如果已经解析过了,报错
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;

// 根节点是 configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

// 解析配置
private void parseConfiguration(XNode root) {
try {
// 分步骤解析
// 1. properties
propertiesElement(root.evalNode("properties"));
// 2. 类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 3. 插件
pluginElement(root.evalNode("plugins"));
// 4. 对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
// 5. 对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 6. 设置
settingsElement(root.evalNode("settings"));
// 7. 环境
environmentsElement(root.evalNode("environments"));
// 8. databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 9. 类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
// 10. 映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

可以看出,XMLConfigBuilder 的 parse 方法分 10 步通过 XML 文件对 Configuration 对象进行解析。解析的过程比较复杂,有些步骤比较关键,一步步来看:

4.2.2.1 properties

解析 properties 的方法如下:

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
// <!--通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的是 properties 属性中指定的属性。-->
// <properties resource="config.properties">
// <!--为属性设置默认值的全局开关。-->
// <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
// <!--<property name="username" value="hyh"/>-->
// <property name="password" value="huan"/>
// </properties>
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 如果在这些地方,属性多于一个的话,MyBatis 按照如下的顺序加载它们:

// 1. 在 properties 元素体内指定的属性首先被读取。
// 2. 从类路径下资源或 properties 元素的 url 属性中加载的属性第二被读取,它会覆盖已经存在的完全一样的属性。
// 3. 作为方法参数传递的属性最后被读取,它也会覆盖任一已经存在的完全一样的属性,这些属性可能是从 properties 元素体内和资源/url 属性中加载的。
// 传入方式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props)

// 1. XNode.getChildrenAsProperties 函数方便得到孩子所有 properties
Properties defaults = context.getChildrenAsProperties();
// 2. 然后查找 resource 或者 url,加入前面的 properties
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 3. variables 也全部加入 properties
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}

可以看出,解析 properties 主要是解析 XML 文件的 properties 节点,把相应的 name 和 value 当做 properties 读取到 defaults 中;然后根据 resource 和 url 属性读取相应的 properties,并读取到 defaults 中;然后把已经在 Configuration 对象的构造方法中设置到 variables 变量的参数 props 读取到 defaults 中;最后把 defaults 设置为 Configuration 对象的 variables 变量和 XPathParser 对象的 variables 变量。

4.2.2.2 类型别名

解析类型别名的方法如下:

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
// <typeAliases>
// <!--<typeAlias alias="Blog" type="com.heyuhuan.model.Blog"/>-->
// <!--每一个在包 com.heyuhuan.model 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。比如 com.heyuhuan.model.Blog 的别名为 blog;若有注解,则别名为其注解值。-->
// <package name="com.heyuhuan.model"/>
// </typeAliases>
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 如果是 package
String typeAliasPackage = child.getStringAttribute("name");
// (一)调用 TypeAliasRegistry.registerAliases,去包下找所有类,然后注册别名(有 @Alias 注解则用,没有则取类的 simpleName)
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 如果是 typeAlias
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
// 根据 Class 名字来注册类型别名
// (二)调用 TypeAliasRegistry.registerAlias
if (alias == null) {
// alias 可以省略
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}

可以看出,解析类型别名主要是把 package 下能扫描到的类或者 typeAlias 下配置的类注册到 typeAliasRegistry 中。typeAliasRegistry 是类型别名注册机,其中有一个 HashMap,维护着别名和类对象的对应关系。如果没有显式地定义 alias,就使用 simpleName 作为 alias。

4.2.2.3 插件

不常用,先略过。

4.2.2.4 对象工厂

不常用,先略过。

4.2.2.5 对象包装工厂

不常用,先略过。

4.2.2.6 设置

解析设置的方法如下:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// <settings>
// <!--该配置影响的所有映射器中配置的缓存的全局开关。-->
// <setting name="cacheEnabled" value="true"/>
// <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。-->
// <setting name="lazyLoadingEnabled" value="true"/>
// <!--当禁用时,会按需加载属性值。-->
// <setting name="aggressiveLazyLoading" value="false"/>
// <!--是否允许单一语句返回多结果集(需要兼容驱动)。-->
// <setting name="multipleResultSetsEnabled" value="true"/>
// <!--使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。-->
// <setting name="useColumnLabel" value="true"/>
// <!--允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。-->
// <setting name="useGeneratedKeys" value="false"/>
// <!--指定 MyBatis 应如何自动映射列到字段或属性。NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。FULL 会自动映射任意复杂的结果集(无论是否嵌套)。-->
// <setting name="autoMappingBehavior" value="PARTIAL"/>
// <!--指定 MyBatis 应如何响应自动映射的未知的列或属性。-->
// <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
// <!--配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新。-->
// <setting name="defaultExecutorType" value="SIMPLE"/>
// <!--设置超时时间,它决定驱动等待数据库响应的秒数。-->
// <setting name="defaultStatementTimeout" value="25"/>
// <!--为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。-->
// <setting name="defaultFetchSize" value="100"/>
// <!--允许在嵌套语句中使用分页(RowBounds)。如果允许,则为 false。-->
// <setting name="safeRowBoundsEnabled" value="false"/>
// <!--允许在嵌套语句中使用分页(ResultHandler)。如果允许,则为 false。-->
// <setting name="safeResultHandlerEnabled" value="true"/>
// <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。-->
// <setting name="mapUnderscoreToCamelCase" value="false"/>
// <!--MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。-->
// <setting name="localCacheScope" value="SESSION"/>
// <!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。-->
// <setting name="jdbcTypeForNull" value="OTHER"/>
// <!--指定哪个对象的方法触发一次延迟加载。-->
// <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
// <!--指定动态 SQL 生成的默认语言。-->
// <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
// <!--指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。-->
// <setting name="callSettersOnNulls" value="false"/>
// <!--当整行属性皆为 null 时,返回 null 对象,或者不返回。-->
// <setting name="returnInstanceForEmptyRow" value="false"/>
// <!--指定 MyBatis 增加到日志名称的前缀。-->
// <setting name="logPrefix" value="MyBatis"/>
// <!--指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
// <setting name="logImpl" value="LOG4J"/>
// <!--指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。-->
// <setting name="proxyFactory" value="JAVASSIST"/>
// <!--指定 VFS 的实现-->
// <!--<setting name="vfsImpl" value=""/>-->
// <!--允许使用方法签名中的名称作为语句参数名称。为了使用该特性,你的工程必须采用 Java 8 编译,并且加上 -parameters 选项。-->
// <!--<setting name="useActualParamName" value="true"/>-->
// </settings>
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// 检查下是否在 Configuration 类里都有相应的 setter 方法(没有拼写错误)
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}

// 下面非常简单,一个个设置属性
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}

可以看出,解析设置主要是对 Configuration 对象的变量进行设置,建议查看官方文档的相关解释

4.2.2.7 环境

解析环境的方法如下:

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
// <environments default="development">
// <environment id="development">
// <transactionManager type="JDBC"/>
// <dataSource type="POOLED">
// <property name="driver" value="${driver}"/>
// <property name="url" value="${url}"/>
// <!--可以在属性后面加上冒号来设置默认值,但先要打开全局开关。-->
// <property name="username" value="${username:hyh}"/>
// <property name="password" value="${password}"/>
// <!--在任意时间可以存在的活动(也就是正在使用)连接数量。-->
// <property name="poolMaximumActiveConnections" value="10"/>
// <!--任意时间可能存在的空闲连接数。-->
// <property name="poolMaximumIdleConnections" value="10"/>
// <!--在被强制返回之前,池中连接被检出(checked out)时间。-->
// <property name="poolMaximumCheckoutTime" value="20000"/>
// <!--如果获取连接花费的相当长的时间,它会给连接池打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直安静的失败)。-->
// <property name="poolTimeToWait" value="20000"/>
// <!--发送到数据库的侦测查询,用来检验连接是否处在正常工作秩序中并准备接受请求。-->
// <property name="poolPingQuery" value="NO PING QUERY SET"/>
// <!--是否启用侦测查询。若开启,也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL)。-->
// <property name="poolPingEnabled" value="false"/>
// <!--配置 poolPingQuery 的使用频度。-->
// <property name="poolPingConnectionsNotUsedFor" value="0"/>
// </dataSource>
// </environment>
// </environments>
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 循环比较 id 是否就是指定的 environment
if (isSpecifiedEnvironment(id)) {
// 事务管理器
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 数据源
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}

可以看出,一个 Configuration 或者一个 SqlSessionFactory 只对应一个 environment;如果要连接两个数据库,那么就要创建两个 SqlSessionFactory。

事务管理器可以选择 JDBC、MANAGED 或者自定义的事务管理器。如果选择 JDBC,那么会直接使用 JDBC 的 commit,rollback,依赖于从数据源得到的连接来管理事务范围。

数据源可以选择 UNPOOLED、POOLED、JNDI 或者任何第三方数据源。如果选择 POOLED,那么会使用有连接池的数据源。

在设置了事务管理器和数据源之后,会使用 Environment 的静态内部类 Builder 来建造一个 Environment 对象,并设置到 Configuration 中。

4.2.2.8 databaseIdProvider

不常用,先略过。

4.2.2.9 类型处理器

不常用,先略过。

4.2.2.10 映射器

映射器的解析方法最为复杂,将在下一篇的源码阅读笔记中记录。