Mybatis源码分析

MyBatis介绍

MyBatis是一个持久层的ORM框架,使用简单,学习成本较低。可以执行自己手写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半自动ORM框架。

Mybaits整体体系图

image.png

例子

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
ini复制代码public class App {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
Reader reader;
try {
//将XML配置文件构建为Configuration配置类
reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
// 数据源 执行器 DefaultSqlSession
SqlSession session = sqlMapper.openSession();
try {
// 执行查询 底层执行jdbc
//User user = (User)session.selectOne("com.xxx.mapper.selectById", 1);

UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println(mapper.getClass());
User user = mapper.selectById(1L);
System.out.println(user.getUserName());
} catch (Exception e) {
e.printStackTrace();
}finally {
session.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

build方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typescript复制代码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.
}
}
}

image.png

可以看到除了XMLConfigBuilder继承于BaseBuilder,还有一些其他的解析配置文件的类。

XMLConfigBuilder.parse方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
csharp复制代码public Configuration parse() {
/**
* 若已经解析过了 就抛出异常
*/
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
/**
* 设置解析标志位
*/
parsed = true;
/**
* 解析我们的mybatis-config.xml的
* 节点
* <configuration>
*
*
* </configuration>
*/
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
ini复制代码private void parseConfiguration(XNode root) {
try {
/**
* 解析 properties节点
* <properties resource="mybatis/db.properties" />
* 解析到org.apache.ibatis.parsing.XPathParser#variables
* org.apache.ibatis.session.Configuration#variables
*/
propertiesElement(root.evalNode("properties"));
/**
* 解析我们的mybatis-config.xml中的settings节点
* 具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
* <settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
..............
</settings>
*
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* 基本没有用过该属性
* VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序
解析到:org.apache.ibatis.session.Configuration#vfsImpl
*/
loadCustomVfs(settings);
/**
* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
* SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
* 解析到org.apache.ibatis.session.Configuration#logImpl
*/
loadCustomLogImpl(settings);
/**
* 解析我们的别名
* <typeAliases>
<typeAlias alias="Author" type="cn.xxx.pojo.Author"/>
</typeAliases>
<typeAliases>
<package name="cn.xxx.pojo"/>
</typeAliases>
解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 解析我们的插件(比如分页插件)
* mybatis自带的
* Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors
*/
pluginElement(root.evalNode("plugins"));

/**
* todo
*/
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置settings 和默认值
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631

/**
* 解析我们的mybatis环境
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="Zw726515"/>
</dataSource>
</environment>

<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
* 解析到:org.apache.ibatis.session.Configuration#environment
* 在集成spring情况下由 spring-mybatis提供数据源 和事务工厂
*/
environmentsElement(root.evalNode("environments"));
/**
* 解析数据库厂商
* <databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySql" value="mysql" />
</databaseIdProvider>
* 解析到:org.apache.ibatis.session.Configuration#databaseId
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**
* 解析我们的类型处理器节点
* <typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
*/
typeHandlerElement(root.evalNode("typeHandlers"));
/**
* 最最最最最重要的就是解析我们的mapper
*
resource:来注册我们的class类路径下的
url:来指定我们磁盘下的或者网络资源的
class:
若注册Mapper不带xml文件的,这里可以直接注册
若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名 同路径
-->
<mappers>
<mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
<mapper class="com.xxx.mapper.DeptMapper"></mapper>


<package name="com.xxx.mapper"></package>
-->
</mappers>
* package 1.解析mapper接口 解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers
2.
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

mapperElement方法

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
ini复制代码private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
/**
* 获取我们mappers节点下的一个一个的mapper节点
*/
for (XNode child : parent.getChildren()) {
/**
* 判断我们mapper是不是通过批量注册的
* <package name="com.xxx.mapper"></package>
*/
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
/**
* 判断从classpath下读取我们的mapper
* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
*/
String resource = child.getStringAttribute("resource");
/**
* 判断是不是从我们的网络资源读取(或者本地磁盘得)
* <mapper url="D:/mapper/EmployeeMapper.xml"/>
*/
String url = child.getStringAttribute("url");
/**
* 解析这种类型(要求接口和xml在同一个包下)
* <mapper class="com.xxx.mapper.DeptMapper"></mapper>
*
*/
String mapperClass = child.getStringAttribute("class");

/**
* 我们得mappers节点只配置了
* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
*/
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
/**
* 把我们的文件读取出一个流
*/
InputStream inputStream = Resources.getResourceAsStream(resource);
/**
* 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
*/
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
/**
* 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
*/
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.");
}
}
}
}
}

addMapper方法

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
typescript复制代码/**
* 方法实现说明:把我们的Mapper class保存到我们的knownMappers map 中
* @param type:我们的Mapper接口
* @return:
* @exception:
* @date:2019/8/22 20:29
*/
public <T> void addMapper(Class<T> type) {
/**
* 判断我们传入进来的type类型是不是接口
*/
if (type.isInterface()) {
/**
* 判断我们的缓存中有没有该类型
*/
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/**
* 创建一个MapperProxyFactory 把我们的Mapper接口保存到工厂类中
*/
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try. mapper注解构造器
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
/**
* 进行解析
*/
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

MapperAnnotationBuilder.parser方法

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
scss复制代码public void parse() {
String resource = type.toString();
// 是否已经解析mapper接口对应的xml
if (!configuration.isResourceLoaded(resource)) {
// 根据mapper接口名获取 xml文件并解析, 解析<mapper></mapper>里面所有东西放到configuration
loadXmlResource();
// 添加已解析的标记
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 获取所有方法 看是不是用了注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// 是不是用了注解 用了注解会将注解解析成MappedStatement
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}

loadXmlResource方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typescript复制代码private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}

XMLMapperBuilder.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
scss复制代码/**
* 方法实现说明:真正的去解析我们的Mapper.xml(EmployeeMapper.xml)
*
* @return:
* @exception:
* @date:2019/8/30 16:43
*/
public void parse() {
/**
* 判断当前的Mapper是否被加载过
*/
if (!configuration.isResourceLoaded(resource)) {
/**
* 真正的解析我们的 <mapper namespace="com.xxx.mapper.EmployeeMapper">
*
*/
configurationElement(parser.evalNode("/mapper"));
/**
* 把资源保存到我们Configuration中
*/
configuration.addLoadedResource(resource);

bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

configurationElement方法

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
csharp复制代码/**
* 方法实现说明:解析我们的<mapper></mapper>节点
* @author:xsls
* @param context document节点
* @return:
* @exception:
* @date:2019/8/31 13:34
*/
private void configurationElement(XNode context) {
try {
/**
* 解析我们的namespace属性
* <mapper namespace="com.xxx.mapper.EmployeeMapper">
*/
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
/**
* 保存我们当前的namespace 并且判断接口完全类名==namespace
*/
builderAssistant.setCurrentNamespace(namespace);
/**
* 解析我们的缓存引用
* 说明我当前的缓存引用和DeptMapper的缓存引用一致
* <cache-ref namespace="com.xxx.mapper.DeptMapper"></cache-ref>
解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 解析我们的cache节点
* <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
解析到:org.apache.ibatis.session.Configuration#caches
org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
*/
cacheElement(context.evalNode("cache"));
/**
* 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 解析我们的resultMap节点
* 解析到:org.apache.ibatis.session.Configuration#resultMaps
* 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
*
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 解析我们通过sql节点
* 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
* 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
* 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 解析我们的select | insert |update |delete节点
* 解析到org.apache.ibatis.session.Configuration#mappedStatements
*/
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

cacheElement方法

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
ini复制代码/**
* 方法实现说明:解析缓存属性
* <cache type=""
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

*
* @param context:cache节点
* @return:
* @exception:
* @date:2019/8/31 14:13
*/
private void cacheElement(XNode context) {
if (context != null) {
//解析cache节点的type属性
String type = context.getStringAttribute("type", "PERPETUAL");
//根据type的String获取class类型
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//获取缓存过期策略:默认是LRU
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
Long flushInterval = context.getLongAttribute("flushInterval");
//size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
Integer size = context.getIntAttribute("size");
//只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//把缓存节点加入到Configuration中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}

useNewCache方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scss复制代码public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}

CacheBuilder.build方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scss复制代码public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}

setStandardDecorators方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
scss复制代码private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);//ScheduledCache:调度缓存,负责定时清空缓存
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) { // 将LRU 装饰到Serialized
cache = new SerializedCache(cache); //SerializedCache:缓存序列化和反序列化存储
}
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}

这边使用装饰者模式对cache进行增强。

buildStatementFromContext方法

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
php复制代码/**
* 方法实现说明:解析我们得得select|update|delte|insert节点然后
* 创建我们得mapperStatment对象
*
* @param list:所有的select|update|delte|insert节点
* @param requiredDatabaseId:判断有没有数据库厂商Id
* @return:
* @exception:
* @date:2019/9/5 21:35
*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
/**
* 循环我们的select|delte|insert|update节点
*/
for (XNode context : list) {
/**
* 创建一个xmlStatement的构建器对象
*/
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

XMLStatementBuilder.parseStatementNode方法

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
ini复制代码public void parseStatementNode() {
/**
* 我们的insert|delte|update|select 语句的sqlId
*/
String id = context.getStringAttribute("id");
/**
* 判断我们的insert|delte|update|select 节点是否配置了
* 数据库厂商标注
*/
String databaseId = context.getStringAttribute("databaseId");

/**
* 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
*/
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

/**
* 获得节点名称:select|insert|update|delete
*/
String nodeName = context.getNode().getNodeName();
/**
* 根据nodeName 获得 SqlCommandType枚举
*/
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
/**
* 判断是不是select语句节点
*/
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
/**
* 获取flushCache属性
* 默认值为isSelect的反值:查询:默认flushCache=false 增删改:默认flushCache=true
*/
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
/**
* 获取useCache属性
* 默认值为isSelect:查询:默认useCache=true 增删改:默认useCache=false
*/
boolean useCache = context.getBooleanAttribute("useCache", isSelect);

/**
* resultOrdered: 是否需要处理嵌套查询结果 group by (使用极少)
* 可以将比如 30条数据的三组数据 组成一个嵌套的查询结果
*/
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

/**
* 解析我们的sql公用片段
* <select id="qryEmployeeById" resultType="Employee" parameterType="int">
<include refid="selectInfo"></include>
employee where id=#{id}
</select>
将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>Node的子节点中
*/
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

/**
* 解析我们sql节点的参数类型
*/
String parameterType = context.getStringAttribute("parameterType");
// 把参数类型字符串转化为class
Class<?> parameterTypeClass = resolveClass(parameterType);

/**
* 查看sql是否支撑自定义语言
* <delete id="delEmployeeById" parameterType="int" lang="xxxLang">
<settings>
<setting name="defaultScriptingLanguage" value="xxxLang"/>
</settings>
*/
String lang = context.getStringAttribute("lang");
/**
* 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
*/
LanguageDriver langDriver = getLanguageDriver(lang);

// Parse selectKey after includes and remove them.
/**
* 解析我们<insert 语句的的selectKey节点, 还记得吧,一般在oracle里面设置自增id
*/
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
/**
* 我们insert语句 用于主键生成组件
*/
KeyGenerator keyGenerator;
/**
* selectById!selectKey
* id+!selectKey
*/
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
/**
* 把我们的命名空间拼接到keyStatementId中
* com.xxx.mapper.Employee.saveEmployee!selectKey
*/
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
/**
*<insert id="saveEmployee" parameterType="com.xxx.entity.Employee" useGeneratedKeys="true" keyProperty="id">
*判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象
*/
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {

/**
* 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,
* 否者就看我们的mybatis-config.xml配置文件中是配置了
* <setting name="useGeneratedKeys" value="true"></setting> 默认是false
* 并且判断sql操作类型是否为insert
* 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
* 否则就是NoKeyGenerator.INSTANCE
*/
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

/**
* 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
* sql脚本对象 . 解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
/**
* STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
*/
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
/**
* 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
*/
Integer fetchSize = context.getIntAttribute("fetchSize");
/**
* 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
*/
Integer timeout = context.getIntAttribute("timeout");
/**
* 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置
*/
String parameterMap = context.getStringAttribute("parameterMap");
/**
* 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
* 可以使用 resultType 或 resultMap,但不能同时使用
*/
String resultType = context.getStringAttribute("resultType");
/**解析我们查询结果集返回的类型 */
Class<?> resultTypeClass = resolveClass(resultType);
/**
* 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
* 可以使用 resultMap 或 resultType,但不能同时使用。
*/
String resultMap = context.getStringAttribute("resultMap");

String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}

/**
* 解析 keyProperty keyColumn 仅适用于 insert 和 update
*/
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");

/**
* 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

以上即是mybatis解析配置文件的全流程源码过程。那么接下来就是openSession打开数据库会话操作。然后数据库操作过程的源码分析。

openSession方法

image.png

有两种实现,进入DefaultSqlSessionFactory类。
image.png

openSessionFromDataSource方法

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
php复制代码/**
* 方法实现说明:从session中开启一个数据源
* @param execType:执行器类型
* @param level:隔离级别
* @return:SqlSession
* @exception:
* @date:2019/9/9 13:38
*/
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);
/**
* 创建一个sql执行器对象
* 一般情况下 若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回
* 一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor
* 详看下面newExecutor方法
*/
final Executor executor = configuration.newExecutor(tx, execType);
/**
* 创建返回一个DeaultSqlSessoin对象返回
*/
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();
}
}

newExecutor方法

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
ini复制代码/**
* 方法实现说明:创建一个sql语句执行器对象
* @param transaction:事务
* @param executorType:执行器类型
* @return:Executor执行器对象
* @exception:
* @date:2019/9/9 13:59
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
/**
* 判断执行器的类型
* 批量的执行器
*/
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
//可重复使用的执行器
executor = new ReuseExecutor(this, transaction);
} else {
//简单的sql执行器对象
executor = new SimpleExecutor(this, transaction);
}
//判断mybatis的全局配置文件是否开启缓存
if (cacheEnabled) {
//把当前的简单的执行器包装成一个CachingExecutor
executor = new CachingExecutor(executor);
}
/**
* TODO:调用所有的拦截器对象plugin方法
*/
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

selectOne方法

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
typescript复制代码/**
* 方法实现说明:查询我们当个对象
* @param statement:我们的statementId(com.xxx.mapper.EmployeeMapper.findOne)
* @param parameter:调用时候的参数
* @return: T 返回结果
* @exception:
* @date:2019/9/9 20:26
*/
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
/**
* 这里selectOne调用也是调用selectList方法
*/
List<T> list = this.selectList(statement, parameter);
//若查询出来有且有一个一个对象,直接返回要给
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
/**
* 查询的有多个,那么久抛出我们熟悉的异常
* Expected one result (or null) to be returned by selectOne(), but found: " + list.size()
*/
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

selectList方法

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
typescript复制代码/**
* 方法实现说明
* @param statement: statementId
* @param parameter:参数对象
* @param rowBounds :mybiats的逻辑分页对象
* @return:
* @exception:
* @date:2019/9/9 20:33
*/
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
/**
* 第一步:通过我们的statement去我们的全局配置类中获取MappedStatement
*/
MappedStatement ms = configuration.getMappedStatement(statement);
/**
* 通过执行器去执行我们的sql对象
* 第一步:包装我们的集合类参数
* 第二步:一般情况下是executor为cacheExetory对象
*/
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

wrapCollection方法

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
typescript复制代码/**
* 方法实现说明:包装我们集合类的参数
* @param object:参数对象
* @return:Object:包装后的对象
* @exception:
* @date:2019/9/9 20:36
*/
private Object wrapCollection(final Object object) {
//若我们的参数类型是Collection
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<>();
//把他key为collection存放到map中
map.put("collection", object);
//若我们参数类型是list类型 把key为list作为集合存放到map中
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
//若是数组,存放key为array的map中
StrictMap<Object> map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}

CachingExecutor.query方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码/**
* 方法实现说明:通过我们的sql执行器对象执行sql
* @param ms 用于封装我们一个个的insert|delete|update|select 对象
* @param parameterObject:参数对象
* @param rowBounds :mybaits的逻辑分页对象 TODO?????
* @param resultHandler:结果处理器对象
* @return:
* @exception:
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/**
* 通过参数对象解析我们的sql详细信息1339025938:1570540512:com.xxx.mapper.selectById:0:2147483647:select id,user_name,create_time from t_user where id=?:1:development
*/
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
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
scss复制代码@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
/**
* 判断我们我们的mapper中是否开启了二级缓存<cache></cache>
*/
Cache cache = ms.getCache();
/**
* 判断是否配置了cache
*/
if (cache != null) {
//判断是否需要刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
/**
* 先去二级缓存中获取
*/
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
/**
* 二级缓存中没有获取到
*/
if (list == null) {
//通过查询数据库去查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//加入到二级缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//没有整合二级缓存,直接去查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

BaseExecutor.query方法

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
scss复制代码@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
//已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// <2> 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// <4.1> 从一级缓存中,获取查询结果
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// <4.2> 获取到,则进行处理
if (list != null) {
//处理存过的
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 获得不到,则从数据库中查询 详细看下面queryFromDatabase方法
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

queryFromDatabase方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scss复制代码private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 详细看下面doQuery方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

SimpleExecutor.doQuery方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ini复制代码public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 详细看下面newStatementHandler方法
StatementHandler handler = configuration.newStatementHandler方法(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//详细看下面prepareStatement方法
stmt = prepareStatement(handler, ms.getStatementLog());
// 详细看下面PreparedStatementHandler的query方法
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

newStatementHandler方法

1
2
3
4
5
ini复制代码public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

prepareStatement方法

1
2
3
4
5
6
7
ini复制代码private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}

PreparedStatementHandler.query方法

1
2
3
4
5
6
java复制代码public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行数据库操作
ps.execute();
return resultSetHandler.handleResultSets(ps);
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%