引言

spring-mybatis.xml中配置为:

1
2
3
4
5
6
7
8
9
<!-- 配置 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置 mybatis-config.xml 路径 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 给 SqlSessionFactory 配置数据源,这里引用上一篇文章的数据源配置 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置 SQL 映射文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>

再来看SqlSessionFactoryBean的类继承关系图:

SqlSessionFactoryBean.png

发现它实现了InitializingBean、FactoryBean、ApplicationListener接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface InitializingBean {
//bean属性设置完成之后回调
void afterPropertiesSet() throws Exception;
}
public interface FactoryBean<T> {
//返回该工厂管理的对象的实例
@Nullable
T getObject() throws Exception;
@Nullable
//返回FactoryBean创建的对象的类型
Class<?> getObjectType();
//这个工厂管理的对象是单例的吗?
default boolean isSingleton() {
return true;
}
}
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
//处理应用程序事件。
void onApplicationEvent(E event);
}

这里简单看一下InitializingBean的afterPropertiesSet()方法是怎么被调用的,回顾一下Spring中bean通过ClassPathXmlApplicationContext的加载流程:

  1. 通过ClassPathXmlApplicationContext的构造函数,调用到其父类AbstractApplicationContext的refresh()方法。
  2. 之后调用AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)获取所有的bean定义。
  3. 进入AbstractBeanFactory的doGetBean(final String name, @Nullable final Class requiredType,@Nullable final Object[] args, boolean typeCheckOnly)方法。
  4. 再进入AbstractAutowireCapableBeanFactory的doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)方法。
  5. 在属性填充之后,调用initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)方法。
  6. 最后进入invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)方法,在这里调用了InitializingBean的afterPropertiesSet()方法。

为什么要看这个InitializingBean的afterPropertiesSet()方法呢,因为org.mybatis.spring.SqlSessionFactoryBean中有个很重要的方法在afterPropertiesSet()中实现。

1
2
3
4
5
6
7
8
9
 @Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//重点!!!
this.sqlSessionFactory = buildSqlSessionFactory();
}

buildSqlSessionFactory方法非常的长,但是很清晰:
创建SqlSessionFactory实例,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 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
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
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//组装Configuration
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
//properties(属性)处理
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
//这里为mybatis-config.xml
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
//VFS:提供一个非常简单的API来访问应用服务器中的资源。
configuration.setVfsImpl(this.vfs);
}
//TypeAlias
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
//plugin
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
//TypeHandler
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
//解析mybatis-config.xml配置文件
xmlConfigBuilder.parse();

if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
//事务
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
//mapper/*.xml
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
//解析
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
public SqlSessionFactory build(Configuration config) {
//看这里,会在SqlSessionFactoryBean中创建一个DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}

这里我只分析几个我感兴趣的部分:

  1. 解析mybatis-config.xml配置文件:xmlConfigBuilder.parse();
  2. 解析mapper/*.xml下的文件

这里在留一个点:mybatis的事务管理是使用自己的还是委托给spring处理的?

解析mybatis-config.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="People" type="com.demo.mybatis.entity.PeopleEntity"/>
</typeAliases>
</configuration>
1
2
3
4
5
6
7
8
9
 public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析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
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

这里的配置信息一定要参考MyBatis官方配置文档
这里我之分析设置的两项:settings和typeAliases,还有个重要的mappers,等后面解析mapper/*.xml下的文件那一部分一块看。

Properties settings = settingsAsProperties(root.evalNode(“settings”));

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
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
//获取xml中setting配置,并转为Properties
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
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).");
}
}
return props;
}
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}

就比如:
将cacheEnabled和true塞到properties中。

typeAliasesElement(root.evalNode(“typeAliases”));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}

这的逻辑也相对简单,源码自带注释。

解析mapper/*.xml下的文件

这块要参考MyBatis官方XML映射文件中文文档
感觉这块才是MyBatis最强大的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 //mapper/*.xml
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
//解析每一个mapper.xml文件
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}

这里有必要简单的贴个PeopleMapper.xml和PeopleMapper.java还有PeopleEntity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.demo.mybatis.mapper.PeopleMapper">
<resultMap id="BaseResultMap" type="com.demo.mybatis.entity.PeopleEntity">
<result column="id" property="id" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
</resultMap>

<select id="query" resultMap="BaseResultMap">
SELECT id,name from people
</select>

</mapper>
1
2
3
4
5
6
7
8
9
10
11
public interface PeopleMapper {
List<PeopleEntity> query();
}

import lombok.Data;

@Data
public class PeopleEntity {
private int id;
private String name;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void parse() {
//检测映射文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
//解析mapper节点
configurationElement(parser.evalNode("/mapper"));
//解析过的mapper文件做记录
configuration.addLoadedResource(resource);
//通过命名空间绑定mapper接口
bindMapperForNamespace();
}

//解析未完成的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

上面的代码涉及到三块重点,一个是mapper.xml的解析,另一个是mapper接口与命名空间绑定,还有一个是解析未完成的节点。

解析映射文件

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
private void configurationElement(XNode context) {
try {

String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置命名空间
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//解析cache
cacheElement(context.evalNode("cache"));
//解析parameterMap,文档说不维护了
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete
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);
}
}

代码很清晰,在看源码的时候一定要看着官方文档,就比如parameterMap节点,已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。文档中不会介绍此元素。
在这里我以PeopleMapper.xml为例子进行解析。
首先namespace为:com.demo.mybatis.mapper.PeopleMapper,之后解析resultMap:

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
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
//解析单个resultMap节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//获取id,例子中为:BaseResultMap
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取type,例子中为:com.demo.mybatis.entity.PeopleEntity
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//我都没用过这个属性
String extend = resultMapNode.getStringAttribute("extends");
//autoMapping 如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。
// 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//获取class
Class<?> typeClass = resolveClass(type);
//鉴别器
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
//constructor - 用于在实例化类时,注入结果到构造方法中
//idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
//arg - 将被注入到构造方法的一个普通结果
if ("constructor".equals(resultChild.getName())) {
//解析constructor节点
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//解析discriminator节点
//discriminator – 使用结果值来决定使用哪个 resultMap
//case – 基于某些值的结果映射
//嵌套结果映射 – case 本身可以是一个 resultMap 元素,因此可以具有相同的结构和元素,或者从别处引用一个
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
//id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
flags.add(ResultFlag.ID);
}
//解析 id 和 property 节点,并生成相应的 ResultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//构建 ResultMap 对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}

我这里也不深入分析了,主要目的是了解整体的框架。

Mapper 接口绑定过程

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
private void bindMapperForNamespace() {
//获取当前的命名空间:
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//获取绑定类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
//看mapper类是否绑定过
if (!configuration.hasMapper(boundType)) {
// Spring可能不知道真正的资源名称,
// 所以我们设置了一个标志来防止再次从mapper接口加载该资源
// 是一个set集合
configuration.addLoadedResource("namespace:" + namespace);
// 绑定mapper
configuration.addMapper(boundType);
}
}
}
}

再看configuration.addMapper(boundType),这个Configuration类是非常重要的类。

1
2
3
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}

会调用到MapperRegistry的addMapper方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public <T> void addMapper(Class<T> 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<T>(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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//支持注解
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

简单总结:
MyBatis对配置文件的解析或者对mapper.java上注解的解析真的很复杂。我这里只为了加深一些MyBatis的使用理解,所以没有太深入的分析,感兴趣的话可以参考官方文档,深入的分析。

tencent.jpg