本专辑将介绍mybatis的原理和源码分析。
1、概述
在不使用spring的情况下,我们从官网上可以看到mybatis的使用方法:
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
我们可以看到,mybatis启动,首先要加载自己的配置文件,通过配置文件创建SqlSessionFactory,然后再使用SqlSessionFactory对象创建SqlSession对象,通过SqlSession对象来操作数据库。本篇先介绍mybatis加载配置文件的过程。
2、SqlSessionFactoryBuilder
首先我们看一下上面例子中SqlSessionFactoryBuilder的build()方法,我们找到这个方法的最终实现:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我们可以看到默认SqlSessionFactoryBuilder创建SqlSessionFactory,是直接实例化了一个DefaultSqlSessionFactory对象,在创建DefaultSqlSessionFactory对象的时候需要一个Configuration对象作为构造方法的参数,我们也可以看到Configuration对象是由XMLConfigBuilder读取mybatis-config.xml这个配置文件的信息创建的,我们可以看一下具体的创建过程,我们看一下XMLConfigBuilder的parse()方法:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 第一步,加载properties节点,把配置文件中定义配置变量解析出来
propertiesElement(root.evalNode("properties"));
// 第二步,加载setting节点,
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 第三步,加载类的别名
typeAliasesElement(root.evalNode("typeAliases"));
// 第四步,加载插件
pluginElement(root.evalNode("plugins"));
// 第五步,加载objectFactory节点
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 第八步,将前面settings节点解析出来的配置设置到Configuration中
settingsElement(settings);
// 第九步,加载environments节点,
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加载配置文件的时候,以configuration节点作为根节点开始解析,首先,加载properties节点,将properties下定义的变量加载到Configuration的variables属性,具体实现比较简单,这里不看源码,有兴趣的朋友可以自己看一下。
第二步,解析settings节点,把配置解析成Properties对象,在mybatis运行的过程中,可以通过修改这些属性来改变mybatis 的行为,具体有哪些配置可以参考Mybatis的文档。这里对vfsImpl参数有个特殊处理,可以指定VFS的实现。
第三步,加载类的别名信息,注册到Configuration的typeAliasRegistry对象中,这个typeAliasRegistry对象中也包含了许多默认的类的别名,如:registerAlias("string", String.class)。注册有两种方式,一种是在<typeAliases></typeAliases>中添加<typeAlias alias="" type=""/>节点,另一种是通过添加<package name="domain.blog"/>直接指定一个包,扫描包中的@Alias("")注解来寻找别名。这些都可以从源码中体现,具体实现源码不再贴出。
第四步,加载mybatis的插件(Interceptor),这里把定义的插件信息读出来,通过反射创建实例,然后注册到Configuration的interceptorChain中。
第五步,加载自定义的ObjectFactory,ObjectFactory是mybatis查询出结果后,创建查询结果使用的对象工厂,默认是直接使用目标类的构造方法进行创建(具体实现可以看一下DefaultObjectFactory这个类),这里用户可以自定义实现。这里把用户自定义的ObjectFactory实现类注册到Configuration的objectFactory属性。
第六步和第七步,分别是加载objectWrapperFactory和reflectorFactory节点,具体过程和加载ObjectFactory节点类似,而且这两个节点在官方文档中没有提及,所以我们大可不必看这两个过程。
第八步,将前面settings节点解析出来的配置设置到Configuration中。
第九步,加载environments节点,environments节点下配置了不同环境下的不同的environment节点,environment节点主要配置了transactionManager和dataSource信息。我们可以看一下具体实现:
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"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
我们可以看到,在加载environment的时候,只会去读取default属性指定的environment节点。environment节点下必须要有transactionManager和dataSource节点,不然读取的时候会抛出异常。读取完这些信息之后,会创建Environment对象,并将该对象设置到Configuration的environment属性。
第十步,加载databaseIdProvider节点,databaseIdProvider的具体作用可以看一下mybatis官方文档,我们可以看一下具体实现:
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).newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
如果定义了databaseIdProvider,mybatis会根据上面environment定义的datasource来选择会用到的databaseId,并设置到configuration的databaseId属性,以供后面加载statements使用。
第十一步,加载typeHandlers,typeHandlers的功能可以看一下mybatis的官方文档。其作用为:在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析的最终结果会被注册到Configuration的typeHandlerRegistry中,mybatis定义了很多类型的默认实现,有兴趣的可以看一下源码。typeHandler也可以通过注解的方式定义,这里就不再多说了。
最后一步是加载mappers节点,我们先来看一下解析mappers节点的源码:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
首先我们可以看到,在解析mappers子节点的时候,解析方法会被分成两大类,一种是从xml文件中解析(url、source),一种是从Class解析(class、package)。从Class解析比较简单,仅仅是将Class注册到Configuration的mapperRegistry属性中。而解析xml比较复杂,我们来看一下具体过程,其中url和resource通过xml方式定义mapper(也就是我们平时使用的XXXMapper.xml文件),解析xml定义的mapper是交给XMLMapperBuilder来完成的,我们看一下XMLMapperBuilder的parse()方法:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 加载mapper节点下的所有元素
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
第一步,加载mapper节点下的所有元素,我们看一下configurationElement()方法的具体实现:
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"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
首先获取mapper的namespace属性,然后将namespace暂存到builderAssistant中,因为在接下来的过程中会频繁用到namespace属性。
接下来解析cache-ref和cache属性,这里很简单,就是单纯的解析节点的属性而已,所以不再赘述,如果不知道cache-ref和cache节点定义的作用的读者,建议去官方文档了解一下这两个标签的作用。
然后是加载parameterMap节点,官方文档已经将这个配置标记为废弃,所以我们可以不用关注这个,我们看一下接下来的解析resultMap的过程。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
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) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
我们可以看到,解析resultMap节点时,首先会解析出id属性,然后再解析出type属性(“ofType”、“resultType”、“javaType”),然后是解析extend属性、autoMapping属性。
接下来就是解析resultMap下的子节点,加载子节点的时候对constructor和discriminator会做特殊处理。constructor和其他的节点一样,都会被解析成一个ResultMapping对象,并加到一个list中。我们可以看一下ResultMapping又那些参数:
private Configuration configuration;
private String property;
private String column;
private Class<?> javaType;
private JdbcType jdbcType;
private TypeHandler<?> typeHandler;
private String nestedResultMapId;
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
private List<ResultFlag> flags;
// 一个<result>节点的column属性中包含多个column的话会被加载成composites
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
private boolean lazy;
加载逻辑比较简单,这里我们不在赘述,有兴趣的同学可以看一下具体实现。
我们再看一下加载discriminator节点为Discriminator对象的逻辑:
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<String, String>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
discriminatorMap.put(value, resultMap);
}
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
这里也很简单,分别读取column、javaType、jdbcType、typeHandler属性,然后读取子节点case节点,把不同的value对应的resultMap解析成一个map,之后创建成一个Discriminator对象返回。
最后,系统会将resultMap节点解析出来的各种属性封装成一个ResultMap注册到configuration中。到此,resultMap节点的解析我们就看完了。
我们接下里回到configurationElement()方法中来,在解析完resultMap节点后,接下里会解析sql节点,这里就是生成一个Map,key是sql的id,value是一个XNode节点的对象,解析过程比较简单,我们就不单独看了。
最后就是解析select、insert、update、delete这些节点了,我们可以看一下buildStatementFromContext()方法的实现,我们一层一层最终,最后可以发现,这些节点是交给XMLStatementBuilder的parseStatementNode()方法来解析的,我们先把源码贴出:
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 此处省略部分代码
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 此处省略部分代码
// 解析节点的子节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析SelectKey节点
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 将节点内容解析成SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 此处省略部分代码
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;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
解析这些节点属性的方法都很简单,大家可以看一下源码具体实现,上面的源码省略了这些过程。而解析这些标签子节点的内容这块比较复杂,我们一起来看一下。我们很容易发现,解析节点内容是通过XMLIncludeTransformer的applyIncludes()方法实现的,我们贴出源码来分析:
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
applyIncludes(source, variablesContext, false);
}
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
if (source.getNodeName().equals("include")) {
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
Properties toIncludeContext = getVariablesContext(source, variablesContext);
applyIncludes(toInclude, toIncludeContext, true);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
applyIncludes(children.item(i), variablesContext, included);
}
} else if (included && source.getNodeType() == Node.TEXT_NODE
&& !variablesContext.isEmpty()) {
// replace variables ins all text nodes
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
我们可以看到,这是一个递归方法,当加载的节点是include节点,或者节点属性是ELEMENT_NODE的时候,会递归执行,直到节点属性是TEXT_NODE节点。这里我们需要了解一下java解析xml的一些知识,我们举个例子:
有一个xml串:
<a>
head
<b></b>
tail
<a/>
在解析a标签的子Node的时候,会解析出三个子Node,也就是说 head和tail都会被解析成一个Node,其类型为TEXT_NODE,而<b>节点会被解析成ELEMENT_NODE节点。
好了,我们再回到mybatis的解析过程中来。我们先看一下TEXT_NODE解析的过程,这个过程只有在included这个参数传入true的时候才会被触发。我们先来看看这个过程做了什么,这个过程是根据我们之前configuration节点下的properties节点解析出的变量,来替换源text文本中的变量(变量以$()这种方式表示),替换过程比较复杂,我们也没必要细看,这里就不多说,我们知道只要知道在解析TEXT_NODE节点的时候会做一个变量替换的过程即可。
而解析ELEMENT_NODE的过程很简单,就是递归调用而已,我们也不多说。
最后我们看一下解析include节点,这一步其实也比较简单,就是把当前的include节点替换成include的目标节点。
我们再回到parseStatementNode()方法,在解析完子节点后,mybatis会处理SelectKey节点,具体方法是:processSelectKeyNodes(),处理过程可以概述为:将SelectKey节点解析成一个MappedStatement对象,然后再将MappedStatement对象封装成SelectKeyGenerator对象,然后根据String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;这个规则生成一个id作为SelectKeyGenerator的id,注册到configuration对象的keyGenerators属性中,最后再把这个SelectKey节点移除,这里我们不贴出源码了,有兴趣的同学可以自己看一下。
接下来我们看到mybatis通过这种方式将节点内容解析成对象:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
LanguageDriver有两个实现类:RawLanguageDriver和XMLLanguageDriver。RawLanguageDriver的注释中写到不推荐使用RawLanguageDriver,除非确定要解析的sql是非动态sql,这里我们只看XMLLanguageDriver的实现就行了。我们一步一步追踪,会发现,创建SqlSource 的过程是交给XMLScriptBuilder类的parseScriptNode()方法实现的,我们来看一下:
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
我们可以看到,mybatis会将标签的子节点解析成一个SqlNode的List,如果子节点是TEXT_NODE和CDATA_SECTION_NODE,则直接封装成一个TextSqlNode,如果是ELEMENT_NODE节点,说明是动态sql节点,这里使用NodeHandler来生成SqlNode对象。我们可以先来看一下SqlNode有多少子类:
这些子类正好对应我们使用mybatis动态sql的时候用的节点标签。SqlNode的具体作用我们留到后面分析,我们暂且可以把SqlNode的作用理解为一个sql对象,这个对象可以根据输入内容生成对应的sql语句。看到这里,我们可以猜测一下,在执行动态sql构建的时候,动态sql的构建是根据List<SqlNode>一步一步创建sql拼接而成的。其实我们从接下里的SqlSource构建过程也可以看出,所有的SqlNode节点会被封装成一个MixedSqlNode节点,这个节点算是所有的SqlNode的一个外观节点:
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
}
生成的MixedSqlNode对象会作为SqlSource的构造方法参数被传入SqlSource,到这里,SqlSource的创建过程我们分析完了。我们继续回到上文提到的parseStatementNode()方法,在解析完各种属性,创建完SqlSource对象后,mybatis会根据解析完的参数生成一个MappedStatement对象添加到Configuration对象的mappedStatements属性中,mappedStatements是一个Map,key是节点的id(id属性会被替换成currentNamespace + "." + base)属性,value就是MappedStatement对象。
从上面的分析,我们看到了mapper节点的解析过程,接下来我们继续回到XMLMapperBuilder的parse()方法,解析完mapper节点之后,mybatis会根据mapper的namespace去寻找对应的Mapper接口,并把接口加载到Configuration的mapperRegistry属性中。这些操作在bindMapperForNamespace()方法中有体现。做完这些动作,parse()方法会调用下面三个方法做一些后续动作,这里我们不再进行详细分析。大该的作用就是将未注册的ResultMap、CacheRefs、Statements注册到Configuration中。
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
到这里Configuration的创建过程分析完了,大家是不是感到很乱?我把Configuration的几个比较重要的属性加了注释,来方便大家理解:
public class Configuration {
/**
* 加载配置文件中的<environment></environment>节点产生的对象,
* 对象持有transactionFactory和dataSource
*/
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
/**
* <properties></properties>节点解析出的值
*/
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
/**
* 解析的<objectFactory></objectFactory>节点生成的objectFactory对象,有默认值
*/
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
/**
* 根据environment节点下配置的DataSource获取的数据源id
*/
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
/**
* 通过类方式(或者扫描包)方式注册的Mapper类接口和通过xml文件注册的namespace对应的接口
*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/**
* 解析的<plugins></plugins>节点注册的插件
*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
/**
* /mapper/parameterMap节点下的typeHandler属性,默认会有一些通用的typeHandler
*/
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
/**
*<typeAliases></typeAliases>节点解析出来的别名信息
*/
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
/**
* 解析的insert、update、delete、select节点,id为 namespace + "." + id
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
/**
* 解析的<resultMap></resultMap>节点,id为 namespace + "." + id
*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
/**
* 解析的<parameterMap></parameterMap>节点,id为 namespace + "." + id
*/
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
/**
* 解析的insert、update、delete、select节点中的<SelectKey></SelectKey>节点,
* key是父节点id(insert、update、delete、select这些节点的id)+"!selectKey"
*/
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
/**
* 已经加载的资源,(xml文件和Class,xml文件添加namespace,Class添加类名)
*/
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
}
SqlSessionFactoryBuilder创建SqlSessionFactory的过程其实最主要是解析配置并生成Configuration对象的过程,创建完Configuration对象之后,SqlSessionFactoryBuilder直接调用DefaultSqlSessionFactory的构造方法来创建SqlSessionFactory对象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
到此我们对Mybatis启动过程中加载配置文件有了一定的了解,后面我们会继续分析mybatis的源码。