一、简介
前面一篇文章我们对Mybatis整体的执行流程做了一个详细的总结,可进入专栏查看;
本篇文章我们将分析一下配置信息是如何解析的以及SqlSessionFactory创建过程。
二、配置信息解析过程
下面我们通过Debug方式点查看Mybatis如何获取配置文件:
//1、读取配置文件 | |
String resource = "mybatis-config.xml"; | |
InputStream inputStream; | |
try { | |
//主要是通过ClassLoader.getResourceAsStream()方法获取指定的classpath路径下的Resource | |
inputStream = Resources.getResourceAsStream(resource); | |
//2、初始化mybatis,创建SqlSessionFactory类实例 | |
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); | |
System.out.println(sqlSessionFactory); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} |
首先读取mybatis-config.xml全局配置文件,转换成文件流。然后第二步是构造SqlSessionFactory。接着查看build方法,我们来到SqlSessionFactoryBuilder的build()方法,利用XMLConfigBuilder方法中的parseConfiguration方法解析相关的配置,inputStream流是mybatis-config.xml配置对应的字节流,XMLConfigBuilder的parse()方法解析mybatis-config.xml节点得到Configuration配置类。
//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream) | |
//传入我们classpath路径下mybatis-config.xml的文件流 | |
public SqlSessionFactory build(InputStream inputStream) { | |
return build(inputStream, null, null); | |
} | |
//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties) | |
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { | |
try { | |
//构造一个XMLConfigBuilder对象,这里用到了建造者设计模式 | |
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); | |
//其中parser.parse()负责解析xml,然后返回configuration对象,接着通过build(configuration)创建SqlSessionFactory | |
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. | |
} | |
} | |
} | |
//parse()方法:负责解析xml,并将解析之后的配置封装到configuration中,返回给build方法,用于创建SqlSessionFactory | |
//org.apache.ibatis.builder.xml.XMLConfigBuilder#parse | |
public Configuration parse() { | |
//判断是否重复解析 | |
if (parsed) { | |
throw new BuilderException("Each XMLConfigBuilder can only be used once."); | |
} | |
parsed = true; | |
//解析mybatis-config.xml中<configuration>标签的内容,封装成XNode对象,然后传入parseConfiguration方法进行具体的配置解析 | |
//parseConfiguration方法负责具体的配置解析 | |
parseConfiguration(parser.evalNode("/configuration")); | |
//返回configuration对象 | |
return configuration; | |
} |
我们debug看一下,parser.evalNode("/configuration")返回的结果如下图所示:
parse()方法里面实际上是调用的parseConfiguration方法,负责解析XML配置。
根据上图configuration标签内我们声明的标签,遍历,挨个进行解析:
//org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration | |
private void parseConfiguration(XNode root) { | |
try { | |
//先解析properties标签 | |
propertiesElement(root.evalNode("properties")); | |
Properties settings = settingsAsProperties(root.evalNode("settings")); | |
loadCustomVfs(settings); | |
loadCustomLogImpl(settings); | |
//typeAliases别名配置解析, 别名配置有两种方式: a.指定单个实体; b.指定包路径 | |
//类型别名是为Java类型设置一个短的名字, 用来减少类完全限定名的冗余 | |
typeAliasesElement(root.evalNode("typeAliases")); | |
//插件解析 | |
pluginElement(root.evalNode("plugins")); | |
//对象工厂解析 | |
objectFactoryElement(root.evalNode("objectFactory")); | |
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); | |
reflectorFactoryElement(root.evalNode("reflectorFactory")); | |
//解析全局配置settings相关配置 | |
settingsElement(settings); | |
// environments里面其实就包含我们的数据库连接信息等的配置,重点,下面详细介绍. | |
environmentsElement(root.evalNode("environments")); | |
databaseIdProviderElement(root.evalNode("databaseIdProvider")); | |
//类型转换器相关配置解析,用于数据库类型和Java数据类型的转换 | |
typeHandlerElement(root.evalNode("typeHandlers")); | |
//mapper接口配置解析,重点,下面详细介绍 | |
mapperElement(root.evalNode("mappers")); | |
} catch (Exception e) { | |
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); | |
} | |
} |
总结:parseConfiguration完成的是解析configuration下的所有标签,主要包括environment、mapper接口、别名等的配置。
接下来我们先来看看Mybatis如何解析environments标签内容的。
- environmentsElement(root.evalNode("environments")),其实就是解析environments,如下对应的配置:
<environment id="development"> | |
<transactionManager type="JDBC"/> | |
<dataSource type="POOLED"> | |
<property name="driver" value="com.mysql.jdbc.Driver"/> | |
<property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis?serverTimezone=UTC"/> | |
<property name="username" value="root"/> | |
<property name="password" value="root"/> | |
</dataSource> | |
</environment> |
查看environmentsElement()方法的源码:
//解析environments | |
private void environmentsElement(XNode context) throws Exception { | |
if (context != null) { | |
if (environment == null) { | |
//development | |
environment = context.getStringAttribute("default"); | |
} | |
//循环遍历XML各个节点 | |
for (XNode child : context.getChildren()) { | |
//获取ID属性,对应<environment id="development">的ID属性, 即id=development | |
String id = child.getStringAttribute("id"); | |
if (isSpecifiedEnvironment(id)) { | |
//获取transactionManager事务管理器相关配置 | |
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); | |
//解析数据源配置,具体见下面分析 | |
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); | |
DataSource dataSource = dsFactory.getDataSource(); | |
Environment.Builder environmentBuilder = new Environment.Builder(id) | |
.transactionFactory(txFactory) | |
.dataSource(dataSource); | |
//将解析出来的数据源配置等信息赋值给configuration对象 | |
configuration.setEnvironment(environmentBuilder.build()); | |
} | |
} | |
} | |
} |
我们看到,数据源的解析其实是在dataSourceElement()方法中进行的:
//org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement | |
//解析dataSource数据源配置 | |
private DataSourceFactory dataSourceElement(XNode context) throws Exception { | |
if (context != null) { | |
String type = context.getStringAttribute("type"); | |
//将解析出来的属性内容封装成Properties对象 | |
Properties props = context.getChildrenAsProperties(); | |
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); | |
factory.setProperties(props); | |
return factory; | |
} | |
throw new BuilderException("Environment declaration requires a DataSourceFactory."); | |
} |
如下图,解析出标签内配置的四个properties属性,并封装成Properties对象,赋值给DataSourceFactory,返回DataSourceFactory对象。
小总结:dataSource数据源解析大体过程为:
- 通过XMLConfigBuilder#environmentsElement方法获取到mybatis-config.xml配置文件中environments标签下的enviroment标签信息;
- 然后通过XMLConfigBuilder#dataSourceElement解析dataSource中的内容;
- 解析出标签内配置的properties属性,并封装成Properties对象;
- 将dataSource中的内容存在在Enviroment的DataSource变量中;
- 将环境配置信息读取后存放在Confiuartion变量中;
this.configuration.setEnvironment(environmentBuilder.build());
以上就是关于数据源配置的解析。接下来我们再看一下mapper接口是如何解析的,其他配置的解析感兴趣的小伙伴可以自行Debug分析。
三、Mapper接口解析过程
对mapper接口的解析,对应的解析方法是XMLConfigBuilder的mapperElement(),具体代码如下:
//mapper接口配置解析 | |
//org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement | |
private void mapperElement(XNode parent) throws Exception { | |
if (parent != null) { | |
//实际上就是获取到:<mapper resource="mapper/UserMapper.xml"/> | |
for (XNode child : parent.getChildren()) { | |
//判断是否是指定mapper包扫描配置,我们这是使用的resource配置 | |
//解析<package name=""/> | |
if ("package".equals(child.getName())) { | |
String mapperPackage = child.getStringAttribute("name"); | |
//mapper接口所在的包扫描路径,实际上底层调用mapperRegistry.addMappers(packageName); | |
configuration.addMappers(mapperPackage); | |
} else { | |
//本示例中使用的就是resources方式: resource = mapper/UserMapper.xml | |
String resource = child.getStringAttribute("resource"); | |
//因为我们没有指定url,所以url = null | |
String url = child.getStringAttribute("url"); | |
//因为我们没有指定class,所以mapperClass = null | |
String mapperClass = child.getStringAttribute("class"); | |
//接下来其实就是判断,mybatis-config.xml配置中我们指定的是哪一种方式配置mapper接口 | |
//主要有四种方式:a.指定包扫描方式 b.指定resource方式 c.指定url方式 d.指定class方式 | |
if (resource != null && url == null && mapperClass == null) { | |
//解析<mapper resource=""></mapper> | |
//b.指定resource方式 | |
ErrorContext.instance().resource(resource); | |
InputStream inputStream = Resources.getResourceAsStream(resource); | |
//封装成XMLMapperBuilder对象 | |
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); | |
mapperParser.parse(); | |
} else if (resource == null && url != null && mapperClass == null) { | |
//解析<mapper url=""></mapper> | |
//c.指定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) { | |
//解析<mapper class=""></mapper> | |
//d.指定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方式为例来看一下Mybatis对 Mapper 映射器的解析过程,主要是调用XMLMapperBuilder的parse()方法进行,这里也是使用了构建者模式。
if (resource != null && url == null && mapperClass == null) { | |
//拿到mybatis-config.xml中配置的mapper路径:resource="mapper/UserMapper.xml" | |
ErrorContext.instance().resource(resource); | |
//通过Resources读取转换成资源文件流 | |
InputStream inputStream = Resources.getResourceAsStream(resource); | |
//使用构建者模式,调用XMLMapperBuilder类的parse()方法进行解析 | |
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); | |
mapperParser.parse(); | |
} | |
//拿到mapper.xml文件之后的具体解析方法 | |
//org.apache.ibatis.builder.xml.XMLMapperBuilder#parse | |
public void parse() { | |
if (!configuration.isResourceLoaded(resource)) { | |
//解析所有的mapper子标签 | |
configurationElement(parser.evalNode("/mapper")); | |
configuration.addLoadedResource(resource); | |
//绑定mapper接口 | |
bindMapperForNamespace(); | |
} | |
parsePendingResultMaps(); | |
parsePendingCacheRefs(); | |
parsePendingStatements(); | |
} |
我们看到,parse()方法先调用了configurationElement()来对具体的mapper子标签内容进行解析,然后调用bindMapperForNamespace()方法进行mapper接口与xml的绑定。
详细代码如下:
//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement | |
//configurationElement():解析的是Mapper.xml的标签 | |
private void configurationElement(XNode context) { | |
try { | |
//获取到namespace命名空间 | |
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")); | |
//缓存配置 | |
cacheElement(context.evalNode("cache")); | |
//解析parameterMap标签 | |
parameterMapElement(context.evalNodes("/mapper/parameterMap")); | |
//解析resultmap标签, 用来描述如何从数据库结果集中来加载对象 | |
resultMapElements(context.evalNodes("/mapper/resultMap")); | |
//解析sql标签,可被其他语句引用的可重用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); | |
} | |
} |
上面的代码中,buildStatementFromContext()方法很重要,实际上MappedStatement对象就是在这里面创建的。我们进入buildStatementFromContext方法:
private void buildStatementFromContext(List<XNode> list) { | |
if (configuration.getDatabaseId() != null) { | |
buildStatementFromContext(list, configuration.getDatabaseId()); | |
} | |
buildStatementFromContext(list, null); | |
} | |
//org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String) | |
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { | |
//list其实就是拿到我们mapper.xml中定义的一个select语句,具体如下图: | |
for (XNode context : list) { | |
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); | |
try { | |
//解析insert/update/select/delete中的标签 | |
statementParser.parseStatementNode(); | |
} catch (IncompleteElementException e) { | |
configuration.addIncompleteStatement(statementParser); | |
} | |
} | |
} |
statementParser.parseStatementNode()才是具体解析增删改查标签的方法,详细代码如下:
//org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode | |
//解析insert/update/select/delete中的标签 | |
public void parseStatementNode() { | |
//对应我们select标签的id属性,即getById | |
//id对应于mapper接口的方法,单个mapper.xml中必须唯一,全路径名加+id作为mappedStatement的ID值,所以必须唯一 | |
String id = context.getStringAttribute("id"); | |
//数据库厂商标识 | |
String databaseId = context.getStringAttribute("databaseId"); | |
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); | |
//高级结果映射,对应外部resultMap的命名引用 | |
String resultMap = context.getStringAttribute("resultMap"); | |
//对应我们select标签的resultType返回类型属性,即com.wsh.mybatis.mybatisdemo.entity.User | |
String resultType = context.getStringAttribute("resultType"); | |
String lang = context.getStringAttribute("lang"); | |
LanguageDriver langDriver = getLanguageDriver(lang); | |
//通过ClassLoader装载我们的User类,即com.wsh.mybatis.mybatisdemo.entity.User | |
Class<?> resultTypeClass = resolveClass(resultType); | |
String resultSetType = context.getStringAttribute("resultSetType"); | |
//默认Statement的类型为PREPARED:预编译类型的statement | |
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); | |
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); | |
//nodeName=select | |
String nodeName = context.getNode().getNodeName(); | |
//拿到当前sql命令类型是哪一种, 主要有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH这几种 | |
//这里我们是SELECT查询 | |
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); | |
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; | |
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); | |
boolean useCache = context.getBooleanAttribute("useCache", isSelect); | |
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); | |
// Include Fragments before parsing | |
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); | |
includeParser.applyIncludes(context.getNode()); | |
// Parse selectKey after includes and remove them. | |
processSelectKeyNodes(id, parameterTypeClass, langDriver); | |
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed) | |
//创建SqlSource | |
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); | |
String resultSets = context.getStringAttribute("resultSets"); | |
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; | |
} | |
//创建MappedStatement对象,并存入configuration中 | |
//MappedStatement其实就是每一个增删改查的标签的里的数据 | |
//最终存放到mappedStatements中,mappedStatements存放的是一个个的增删改查 | |
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, | |
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, | |
resultSetTypeEnum, flushCache, useCache, resultOrdered, | |
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); | |
} |
我们看到,parseStatementNode()方法前面都是对select标签体能设置的一些属性的解析,在方法的最后面,调用了addMappedStatement(),看名字,我们也能够猜到MappedStatement就是在这方法里面创建的。
//org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String) | |
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对象中 | |
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 | |
MappedStatement statement = statementBuilder.build(); | |
//将MappedStatement添加到configuration对象中 | |
configuration.addMappedStatement(statement); | |
return statement; | |
} |
继续查看configuration.addMappedStatement(statement)方法的实现:
//org.apache.ibatis.session.Configuration#addMappedStatement | |
public void addMappedStatement(MappedStatement ms) { | |
mappedStatements.put(ms.getId(), ms); | |
} |
Debug图如下所示:
其中Map<String, MappedStatement> mappedStatements是configuration类中的一个成员属性,里面存放着我们所有mapper接口以及对应的SQL信息的绑定信息。为什么使用map来存放,其实就是为了后面执行具体的mapper方法的时候,从mappedStatements根据【namespace+方法名称】作为key,从mappedStatements中进行获取到MappedStatement对象,进而就能获取到对应的SQL已经参数信息,自然就能执行SQL了,以上就是MappedStatement的生成过程。
四、Mapper接口与MapperProxyFactory的绑定过程
前面我们分析了XMLMapperBuilder的parse()方法对mapper接口的解析过程,在执行完configurationElement()方法后还调用了bindMapperForNamespace()方法将mapper接口与MapperProxyFactory绑定绑定的过程。
下面我们看一下bindMapperForNamespace()的源码:
//org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace | |
private void bindMapperForNamespace() { | |
//获取到当前mapper接口的命名空间:com.wsh.mybatis.mybatisdemo.mapper.UserMapper | |
String namespace = builderAssistant.getCurrentNamespace(); | |
if (namespace != null) { | |
Class<?> boundType = null; | |
try { | |
//通过classloader生成对应的Class对象 | |
boundType = Resources.classForName(namespace); | |
} catch (ClassNotFoundException e) { | |
//ignore, bound type is not required | |
} | |
if (boundType != null) { | |
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); | |
//绑定mapper接口,将mapper接口添加到configuration全局配置中 | |
configuration.addMapper(boundType); | |
} | |
} | |
} | |
} |
可以看到,真正调用的是configuration.addMapper()方法:
//org.apache.ibatis.session.Configuration#addMapper | |
public <T> void addMapper(Class<T> type) { | |
//mapperRegistry是Configuration类的一个成员属性 | |
//protected final MapperRegistry mapperRegistry = new MapperRegistry(this); | |
mapperRegistry.addMapper(type); | |
} | |
//org.apache.ibatis.binding.MapperRegistry#addMapper | |
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 { | |
//将mapper接口作为key,存入knownMappers中 | |
//knownMappers是一个map, Map<Class<?>, MapperProxyFactory<?>> knownMappers | |
//接口类型(key)->工厂类的映射关系 | |
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. | |
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); | |
parser.parse(); | |
loadCompleted = true; | |
} finally { | |
if (!loadCompleted) { | |
knownMappers.remove(type); | |
} | |
} | |
} | |
} |
Debug观察一下:
前面我们分析了数据源怎么解析的,还有mapper接口是如何解析的,并且如何生成MappedStatement的,分析完后,我们回到最开始解析XML那里,执行完parseConfiguration(),已经把解析出来的配置都封装进Configuration 对象了,所以XMLConfigBuilder的parse()方法执行完成之后,会返回configuration对象,然后传入SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)中构建SqlSessionFactory对象,具体代码如下:
public SqlSessionFactory build(Configuration config) { | |
return new DefaultSqlSessionFactory(config); | |
} |
如下图,可以看到,所有相关配置解析出来之后都保存在了SqlSessionFactory 的成员变量configuration中,然后通过configuration创建了DefaultSqlSessionFactory对象。至此,我们的SqlSessionFactory对象就算构建成功了。
小总结:
XMLMapperBuilder.parse()方法,是对 Mapper接口的解析,里面有两个方法:
- (1)configurationElement():解析所有的子标签,最终解析Mapper.xml中的insert/update/delete/select标签的id(全路径)组成key和整个标签和数据连接组成MappedStatement存放到Configuration中的 mappedStatements这个map里面;
- (2)bindMapperForNamespace():是把mapper接口和MapperProxyFactory绑定起来,然后存放在MapperRegistry中的knownMappers成员属性里面;