Mybatis源码解析(一)--SqlSessionFactory

写这篇文章主要因为觉得自己总是只知其然,而不知其所以然!所以才写下这篇文章,其主要目的是帮助自己巩固加深所学习的知识。以后也会写更多这样的文章,希望能够帮到自己和其他人能够知其然,也要知其所以然。

Mybatis流程图

Myabtis流程图
上图描述了Mybatis的一个整体流程图,包括从SqlSessionFactory的创建一直到Statment执行Sql,并且返回相关的结果,右边一部分是运行期间要使用到的额外对象。后边的分析也会围绕这个流程图进行分析,首先让我们进入到一个环节,创建SqlSessionFactory对象。

SqlSessionFactory

SqlSessionFacory通过SqlSessionFactoryBulid.build(InputStream in)创建,是整个Mybatis框架的入口,是个应用程序级别的对象,什么意思呢?就是整个应用期间只需要创建一个SqlSessionFactory对象,可以使用单例模式创建这个SqlSessionFactory对象。在与Spring整合之后,我们可以不用显示去创建这个对象,Spring会帮我们进行创建维护。以下是对SqlSessionFactory的创建:

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
public class SqlSessionFactoryBuilder {

public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
/**
* 通过字节流创建SqlSessionFactory对象。
* @param inputStream mybatis-config.xml的字节流
* @param environment mybatis-config.xml中的environment要素的id值
* @param properties 需要加载的properties文件
* @return SqlSessionFactory
*/
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//XMLConfigBuilder是一个解析XML文档的工具类,这里用于解析mybatis-config.xml配置
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//parser.parse()将Mybatis-config.xml、Mapper.xml等解析为Configuration对象
//parser.parse()内部的解析比较复杂,之后会进行详细的分析
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);
}
}

从上述源码中,我们发现SqlSessionFactory中有个非常重要的对象Configuration,它的作用非常的打,维护了mybatis-config.xml的配置信息、Mapper.xml转换成的MapperStatement集合信息、resultMap转换成的ResultMap集合信息、Cache缓存集合信息等。那到底XMLConfigBuilder类是怎样将字节流转换为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
65
66
67
68
69
70
71
72
73
public class Configuration {
//以下我对Mybatis-config.xml的配置称之为mybatis配置

// 环境信息,对应mybatis配置environment元素
protected Environment environment;
// 是否允许在嵌套语句中设置分页,默认为false。对应于mybatis配置中的settings中的safeRowBoundsEnabled属性
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
// 是否开启驼峰命名规则,默认为false。对应于mybatis配置中的settings中的mapUnderscoreToCamelCase属性
protected boolean mapUnderscoreToCamelCase;
// 设置为true时,调用任意延迟属性会默认加载对应对象全部的属性
protected boolean aggressiveLazyLoading;
// 是否允许单一语句返回多条结果集
protected boolean multipleResultSetsEnabled = true;
// 是否允许驱动自动生成主键,默认为false
protected boolean useGeneratedKeys;
//是否使用列标签代替列名,对应于mybatis配置中的settings中的useColumnLabel属性
protected boolean useColumnLabel = true;
// 是否开启缓存,对应于mybatis配置中的settings中的cacheEnabled属性
protected boolean cacheEnabled = true;
// 当返回的结果集存在null时,是否执行setter方法,默认为false。
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow; //
// mybatis自身没有日志,借助于slf4j,可以通过这个设置mybatis的日志前缀。
protected String logPrefix;
// log的实现类
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
// 设置默认的缓存级别,默认为session,即同一个sqlSession对象的查询结果可以复用
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" }));
// 默认的statement超时时间
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; //执行器类型,默认为simple
// 指定结果集的映射,None表示取消映射,PARTIAL标识自动映射没有嵌套结果集的映射,FULL表示会映射复杂的嵌套结果集。
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 标识对查找不到的列名采用不映射结果。
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// 对应于mybatis配置中的properties中的相关属性
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// mybatis配置中的对象工厂,用于在返回结果创建实例时采用该工厂实现。
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//是否开启延迟加载
protected boolean lazyLoadingEnabled = false;
//开启延迟加载时,采用的代理工厂,是CGLIB还是Java动态代理
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
protected String databaseId;
protected Class<?> configurationFactory;
// mapper配置的注册器,用于解析mapper配置使用package的方式时使用。
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 类型处理器的注册器,用于解析类型处理器配置时使用package的方式时使用。
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//类型别名的注册器, 同上
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// 用于存储mapper.xml中的相关配置,key为mapper.xml中的namespace+id,value为mapper解析之后的MapperStatement读写
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
// 用于存储二级缓存中的数据信息,key为mapper.xml中的namespace,value为查询的数据结果集缓存
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
// 用于存储处理器映射器,key为mapper.xml中的namespace+resultMap的id属性值,value为ResultMap的解析后的对象
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>();
}

首先我们先看下上述中parser.parse()方法:

1
2
3
4
5
6
7
8
9
10
public Configuration parse() {
//parsed作为一个类变量,用于判断该配置是否已经被解析过,如果解析过了,则抛出异常!
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//parser.evalNode("/configuration")主要是寻找mybatis-config.xml中configuration元素的节点信息(XNode)。
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

上述方法即是通过XPathParser实例去解析configuration根节点信息,因为本文主要是将Mybatis的相关原理,则不去介绍XPathparser的用法,如果大家想要了解,可以看下源码或者去看下这篇文章[]。当然,如果后边有机会,我也会写一篇关于这个XPathParser的源码解析以及用法。

继续我们上述源码的解析,发现调用了parseConfiguration()方法,具体的源码如下:

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
private void parseConfiguration(XNode root) {
try {
//解析mybatis-config.xml的properties元素信息
propertiesElement(root.evalNode("properties"));
//解析mybatis-config.xml的settings元素信息
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析mybatis-config.xml的typeAliases元素信息
typeAliasesElement(root.evalNode("typeAliases"));
//解析mybatis-config.xml的plugins元素信息
pluginElement(root.evalNode("plugins"));
//解析mybatis-config.xml的objectFactory元素信息
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//将上述解析到的
settingsElement(settings);
//解析mybatis-config.xml的environments(环境)元素信息
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析mybatis-config.xml的typeHandlers(类型处理器)元素信息
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mybatis-config.xml的Mapper(mapper.xml)元素信息
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

看到上述代码,我们会发现其实就是在解析XML文档,然后在设置到Configuration对象中,没有什么过多分析的内容,我们就分析下其中最重要的mapperElement(root.evalNode(“mappers”))方法。具体的源码如下:

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
//解析mybatis.xml配置的mapper元素的配置
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果mapper的配置采用package的方式,则使用Configuration实例的addMappers()方法
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

通过上边的代码我们发现上述代码是获取mapper元素中的resource/class/url属性,然后创建XMLMapperBuilder对象来解析mapper元素属性所对应的数据源(mapperParser.parse()),然后构造MapperStatement对象。接下来,我们继续看mapperParser.parse()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void parse() {
//判断数据源是否已加载过
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml中的相关配置
configurationElement(parser.evalNode("/mapper"));
//将加载过的mapper.xml数据源添加到configuration对象中
configuration.addLoadedResource(resource);
//
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

上边的代码发现如果未加载解析过数据源,则先解析Mapper.xml数据源,并且将该命名空间添加到configuration对象中,如果已加载过数据源,则将结果集对象、缓存对象添加到MapperBuilderAssistant中,最后将其解析为MapperStatement对象添加到Configuration对象中。

configurationElement(parser.evalNode(“/mapper”))方法就不在这里做解析了,主要是解析其中的各个元素将其添加到MapperBuilderAssistant对象中。其中最主要的是parsePendingStatements()方法,该方法中调用了XMLStatementBuilder.parseStatementNode()方法,将其封装为MapperStatement对象。接下来,我们主要看下Mybatis是如何构造MapperStatement对象:

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
public void parseStatementNode() {
//获取select/update/insert/delete中的Id
String id = context.getStringAttribute("id");
//获取数据库Id
String databaseId = context.getStringAttribute("databaseId");
//如果数据库Id不匹配,则返回
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//尝试影响数据库驱动批量获取记录数
Integer fetchSize = context.getIntAttribute("fetchSize");
//数据库驱动等待数据库返回请求结果的超时时间
Integer timeout = context.getIntAttribute("timeout");
//获取输入参数的映射
String parameterMap = context.getStringAttribute("parameterMap");
//获取输入参数的类型
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
//获取输出参数的映射
String resultMap = context.getStringAttribute("resultMap");
//获取输出参数的类型
String resultType =
context.getStringAttribute("resultType");
//
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
//获取执行器的类型;statement、prepared、callable的一个
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//获取节点的类型,是插入/删除/还是新增/修改
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//判断是否是查询操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//判断是否启用缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//判断该SQL查询是否使用缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

processSelectKeyNodes(id, parameterTypeClass, langDriver);
//创建SqlSource对象,该对象主要将配置类、节点信息和输入参数类型包装起来,之后的SqlSession执行时会调用。
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
//获取keyProperty属性,该属性主要针对insert中的selectKey的属性,目的是将其自增的主键赋值给java对象中
String keyProperty = context.getStringAttribute("keyProperty");
//只针对特殊的数据库使用
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//构建MapperStatement对象。
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

上述的代码获取了Mapper.xml中关于insert、update、delete、select中元素的属性,并在最后调用了builderAssistant.addMappedStatement方法,分析到这里,我们已经很接近MapperStatement对象创建并将其添加到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
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {

if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}

id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);

ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}

MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}

这段代码我们可以看到主要是将获取到的各个属性设置到Mapperstatement对象中,使用了建造者模式进行最后的创建。其中还涉及到了输入参数该取何值的情况,如果设置parameterMap,则取parameterMap的,如果未设置parameteMap设置了parameterType,则将其解析为ParameterMap类型,将其添加到MapperStatement对象中。

分析到这里,Mybatis的SqlSessionFactory的创建就分析完了,其中的代码比较多,解释比较少,因为我觉得上边的代码大部分都能看得懂,当然其中还有一些代码因为篇幅等原因我没有在这个章节分析,例如:SqlSource的创建、Xml的解析等,这些会在后续的Mybatis源码解析中讲解,请大家继续关注。

最后做一个总结,SqlSessionFactory的创建主要是通过SqlSessionFactoryBuilder.build(Stream in)方法创建,其中包含一个至关重要的对象Configuration对象,Configuration主要包含了Mabatis.xml中的配置信息、Mapper.xml转换成的MapperStatement集合、ResultMap集合、ParameterMap集合等,而MapperStatement对象包含了每个SQL的相关配置信息。在之后的SqlSession的查询,修改中,Configuration和MapperStatement对象起着至关重要的作用,所以大家还是有必要了解这块的内容的。最后,祝大家的编程水平节节高,工资翻番!