MyBatis源码解读(一)配置加载

MyBatis源码解读之配置加载

MyBatis源码之配置加载

从编程式的demo入手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void test3() throws Exception
{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象 全局配置文件解析 映射文件解析
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserByName("zhangsan");
System.out.println("查询结果");
for (User user : list)
{
System.out.println(user);
}
// 5.关闭会话
sqlSession.close();
}

思考以下几个问题

第一步获取配置文件,这个就不再解读了,就是读取文件流。

第二步,我们创建一个工厂类,配置文件的解析就是在这一步完成的,包括mybatis-config.xml和Mapper映射器文件。

这一步我们关心的内容:解析的时候做了什么,产生了什么对象,解析的结果存放到了哪里。

解析的结果决定着我们后面有什么对象可以使用,和到哪里去取。

第三步,通过SqlSessionFactory创建一个SqlSession。

问题:SqlSession上面定义各种增删改查用的。返回了什么实现类?除了SqlSession,还创建了什么对象,创建了什么环境?

第四步,获得一个Mapper对象。

问题:Mapper是一个接口,没有实现类,是不能被实例化的,那获取到的这个Mapper对象是什么对象?为什么要从SqlSession里面去获取?为什么传进去一个接口,然后还要用接口类型来接收?

这一步实际做的事情是执行SQL,那它又是根据什么找到XML映射器里面的SQL的?此外,我们的方法参数(对象或者Map)是怎么转换成SQL参数的?获取到的结果集是怎么转换成对象的?

最后一步,关闭session,这一步是必须要做的。

带着这几个问题,来看一下源码

配置解析过程

1
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

首先我们new了一个Sq|SessionFactoryBuilder,这是建造者模式的运用(建造者模式用来创建复杂对象,而不需要关注内部细节,是一种封装的体现)。MyBatis中很多地方用到了建造者模式(名字以Builder结尾的类还有9个)。

SqlSessionFactoryBuilder中用来创建Sq|SessionFactory对象的方法是build(),build()方法有9个重载,可以用不同的方式来创建SqlSessionFactory对象。SqlSessionFactory对象默认是单例的。
image-20210523191945988

进入build()方法,最后走到这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最终返回一个 DefaultSqlSessionFactory >>
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.
}
}
}

这里面创建了一个XMLConfigBuilder对象(用来存放所有配置信息的Configuration对象也是这个时候创建的)。

XMLConfigBuilder是抽象类BaseBuilder的一个子类,专门用来解析全局配置文件,针对不同的构建目标还有其他的一些子类(关联到源码路径),比如

XMLMapperBuilder:解析Mapper映射器

XMLStatementBuilder:解析增删改查标签

XMLScriptBuilder:解析动态SQL

最后会调用parse()方法:

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;
// XPathParser,dom 和 SAX 都有用到 >>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

首先会检查全局配置文件是不是已经解析过 ,也就是说在应用的生命周期里面,config配置文件只需要解析一次,生成的Configuration对象也会存在应用的整个生命周期中。

接下来就是parseConfiguration方法:

1
parseConfiguration(parser.evalNode("/configuration"));

解析XML有很多方法,MyBatis对dom和SAX做了封装,方便使用。

这下面有十几个方法,对应着config文件里面的所有一级标签。

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
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 对于全局配置文件各种标签的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 读取文件
loadCustomVfs(settings);
// 日志设置
loadCustomLogImpl(settings);
// 类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于创建对象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于对对象进行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 子标签赋值,默认值就是在这里提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 创建了数据源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

propertiesElement()

第一个是解析<properties>标签,读取我们引入的外部配置文件,例如db.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
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 创建了一个 Properties 对象,后面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
// url 和 resource 不能同时存在
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 加载resource或者url属性中指定的 properties 文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
// 和 Configuration中的 variables 属性合并
defaults.putAll(vars);
}
// 更新对应的属性信息
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}

这里面又有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径的(url)。

解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面(Hashtable对象,KV存储),最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。

settingsAsProperties()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 获取settings节点下的所有的子节点
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;
}

第二个,把<settings>标签也解析成了一个Properties对象,对于<settings>标签的子标签的处理在后面(先解析,后设置)。

1
Properties settings = settingsAsProperties(root.evalNode("settings"));

loadCustomVfs(settings)

1
2
3
4
5
6
7
8
9
10
11
12
13
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}

loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。
根据<settings>标签里面的<vfslmpl>标签,生成了一个抽象类VFS的子类,在 MyBatis中有JBoss6VFS和DefaultVFS两个实现,在io包中,最后赋值到Configuration中。

loadCustomLogImpl

1
2
3
4
5
6
private void loadCustomLogImpl(Properties props) {
// 获取 logImpl设置的 日志 类型
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
// 设置日志
configuration.setLogImpl(logImpl);
}

loadCustomLoglmpl是根据<loglmpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括LOG4J,L0G42,SLF4J等等,在logging包中。

typeAliasesElement()

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 void typeAliasesElement(XNode parent) {
// 放入 TypeAliasRegistry
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) {
// 扫描 @Alias 注解使用
typeAliasRegistry.registerAlias(clazz);
} else {
// 直接注册
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}

这一步解析的是类型别名,它有两种定义方式,一种是直接定义一个类的别名(例如com.domain.Blog定义成blog),一种就是指定一个package,那么这个包下面所有的类的名字就会成为这个类全路径的别名。

类的别名和类的关系,放在一个TypeAliasRegistry对象里面。

pluginElement()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取<plugin> 节点的 interceptor 属性的值
String interceptor = child.getStringAttribute("interceptor");
// 获取<plugin> 下的所有的properties子节点
Properties properties = child.getChildrenAsProperties();
// 获取 Interceptor 对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 设置 interceptor的 属性
interceptorInstance.setProperties(properties);
// Configuration中记录 Interceptor
configuration.addInterceptor(interceptorInstance);
}
}
}

接下来就是解析<plugins>标签,比如Pagehelper的翻页插件,或者我们自定义的插件。<plugins>标签里面只有<plugin>标签,<plugin>标签里面只有<property>标签。

因为所有的插件都要实现Interceptor接口,所以这一步的事情就是把插件解析成Interceptor类,设置属性,然后添加到Configuration的InterceptorChain属性里面,它是一个List。

objectFactoryElement()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
// 获取<objectFactory> 节点的 type 属性
String type = context.getStringAttribute("type");
// 获取 <objectFactory> 节点下的配置信息
Properties properties = context.getChildrenAsProperties();
// 获取ObjectFactory 对象的对象 通过反射方式
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
// ObjectFactory 和 对应的属性信息关联
factory.setProperties(properties);
// 将创建的ObjectFactory对象绑定到Configuration中
configuration.setObjectFactory(factory);
}
}

ObjectFactory用来创建返回的对象。

objectWrapperFactoryElement()

1
2
3
4
5
6
7
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
}

ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。

reflectorFactoryElement()

1
2
3
4
5
6
7
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setReflectorFactory(factory);
}
}

ReflectorFactory是反射的工具箱,对反射的操作进行了封装

以上几个对象都是使用resolveClass创建的

1
2
3
4
(Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
(ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
(ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
(ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();

settingsElement(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
27
28
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
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"), false));
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.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
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.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

这里就是对<settings>标签里面所有子标签的处理了,前面我们已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。settings二级标签中一共26个配置,比如二级缓存、延迟加载、默认执行器类型等等。

需要注意的是,我们之前提到的所有的默认值,都是在这里赋值的。如果说后面我们不知道这个属性的值是什么,也可以到这一步来确认一下。

所有的值,都会赋值到Configuration的属性里面去。

environmentsElement()

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 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");
if (isSpecifiedEnvironment(id)) {
// 事务工厂
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 数据源工厂(例如 DruidDataSourceFactory )
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 数据源
DataSource dataSource = dsFactory.getDataSource();
// 包含了 事务工厂和数据源的 Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 放入 Configuration
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}

这一步是解析<environments>标签。
一个environment就是对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。

databaseldProviderElement()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}

解析databaseldProvider标签,生成DatabaseldProvider对象(用来支持不同厂商的数据库)。

typeHandlerElement()

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 void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}

跟TypeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系,存放在TypeHandlerRegistry对象里面。

这三个对象(Java类型,JDBC类型,Handler)的关系怎么映射?(Map里面再放一个Map)

mapperElement()

http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers
最后就是<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
33
34
35
36
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 不同的定义方式的扫描,最终都是调用 addMapper()方法(添加到 MapperRegistry)。这个方法和 getMapper() 对应
// package 包
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) {
// resource 相对路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析 Mapper.xml,总体上做了两件事情 >>
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// url 绝对路径
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 单个接口
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.");
}
}
}
}
}

根据全局配置文件中不同的注册方式,用不同的方式扫描,但最终都是做了两件事情,对于语句的注册和接口的注册。

扫描类型 含义
resource 相对路径
url 绝对路径
package
class 单个接口

mapperParser.parse()

先从解析Mapper.xml的mapperParser.parse()方法入手。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void parse() {
// 总体上做了两件事情,对于语句的注册和接口的注册
if (!configuration.isResourceLoaded(resource)) {
// 1、具体增删改查标签的解析。
// 一个标签一个MappedStatement。 >>
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
// 一个namespace 一个 MapperProxyFactory >>
bindMapperForNamespace();
}

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

configurationElement() 解析所有的子标签,最终获得MappedStatement对象。

bindMapperForNamespace() 把namespace(接口类型)和工厂类MapperProxyFactory绑定起来

(1) configurationElement()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
// 添加缓存对象
cacheRefElement(context.evalNode("cache-ref"));
// 解析 cache 属性,添加缓存对象
cacheElement(context.evalNode("cache"));
// 创建 ParameterMapping 对象
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 创建 List<ResultMapping>
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析可以复用的SQL
sqlElement(context.evalNodes("/mapper/sql"));
// 解析增删改查标签,得到 MappedStatement >>
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);
}
}

configurationElement是对Mapper.xml中所有具体标签的解析,包括namespace、 cache、parameterMap、 resultMap、sql和select|insert|update|delete。

在buildStatementFromContext() 方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到 mappedStatements中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 解析 Statement >>
buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 用来解析增删改查标签的 XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析 Statement,添加 MappedStatement 对象 >>
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

(2) bindMapperForNamespace()

主要是是调用了addMapper()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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) {
// 判断 在MapperRegistry中是否注册的有当前类型的 MapperProxyFactory对象
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 添加到 MapperRegistry,本质是一个 map,里面也有 Configuration >>
configuration.addMapper(boundType);
}
}
}
}

addMapper() 方法中,把接口类型注册到MapperRegistry中,实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 检测 type 是否为接口
if (hasMapper(type)) { // 检测是否已经加装过该接口
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
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.

// 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

注册了接口之后,开始解析接口类和所有方法上的注解,例如@CacheNamespace、@Select。

此处创建了一个MapperAnnotationBuilder专门用来解析注解。

(3)parse

parse() 方法中的parseCache()和parseCacheRef()方法其实是对@CacheNamespace和@CacheNamespaceRef这两个注解的处理。

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
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 先判断 Mapper.xml 有没有解析,没有的话先解析 Mapper.xml(例如定义 package 方式)
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 处理 @CacheNamespace
parseCache();
// 处理 @CacheNamespaceRef
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();
}

parseStatement() 方法里面的各种getAnnotation(),都是对相应的注解的解析,比如@Options,@SelectKey,@ResultMap等等。

最后同样会创建MappedStatement对象,添加到MapperRegistry中。也就是说在XML中配置,和使用注解配置,最后起到一样的效果。

(4) build

1
2
3
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

Mapper.xml解析完之后,调用另一个build() 方法,返回SqlSessionFactory的默认实现类DefaultSqISessionFactory。

总结

在这一步,mybatis主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。

得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。

最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。

打赏

请我喝杯咖啡吧~

支付宝
微信