我们通过hello world来调试mybatis源码,先写一段伪代码:
//1.通过输入流解析xml配置文件
InputStream inputstream = Resources.getResourceAsStream("xxx.xml")
SqlsessionFactory sqlsessionfactory = new SqlsessionFactoryBuilder().build(inputstream);
//2.获取和数据库的链接,创建会话
SqlSession openSession = sqlsessionfactory.openSession();
//3.获取接口的实现类对象,会为接口自动的创建一个代理对象mapper,代理对象会去执行增删改查方法
xxxMapper mapper = openSession.getMapper(xxxMapper.class)
//4.执行增删改的方法
这就是基本的执行mybatis的四个步骤了,我们从第一步开始讲解,这四步讲解完了mybatis框架也就可以了解个大概了
1.构建SqlsessionFactory
1.1:SqlsessionFactory
是Mybatis的核心类之一,其最重要的功能是提供接口SqlSession, 首先通过输入流将配置文件加载到SqlsessionFactoryBuilder
的build
方法中,我们进入build
方法:
1.2:调用了重载的build方法,我们发现这个方法创建了解析器XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
来解析我们的配置文件,这个类继承public class XMLConfigBuilder extends BaseBuilder
,我们来看看这个解析器是怎样实现的this.build(parser.parse());
,进入parse()方法:
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
这个方法又调用了this.parseConfiguration(this.parser.evalNode("/configuration"));
注意这个方法的意思是解析配置文件的意思,其中的参数"/configuration"
是我们创建mybatis.xml的根标签:
1.3:我们接着看parseConfiguration
方法
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
怎么样,是不是感觉非常熟悉,是的,这个方法将会把加载的根节点里的子节点一一解析,我们不妨进入一个方法看看:
private void settingsElement(Properties props) throws Exception {
this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));
this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
这里只截取了一部分代码,可以看到这个方法会将我们设置的或者没设置的(设置为默认值)标签一一计算并保存到configuration
这个对象中:package org.apache.ibatis.session.Configuration;
1.4:上面我们说了XMLConfigBuilder
会将我们配置文件中的标签一一解析并保存在Configuration
对象中,而我们最需要关心的是映射器,也就是Java接口和XML文件(或注解)共同组成的,也可以理解为一些绑定了映射语句的接口的实现,所以我们接着上面parseConfiguration
方法接着看this.mapperElement(root.evalNode("mappers"));
这个解析mapper的方法:
首先拿到mapper根标签,再判断根标签是怎样加载的,package或是resource,我们这里是通过resource引入mapper映射文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
1.5:我们用resource方式引入来解释增删改是怎样执行的,截取上面方法中的代码:
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
在这段代码中的第一个判断语句中进入mapperParser.parse();
这个方法,似曾相识的是,这个mapperParser
也是通过XMLMapperBuilder创建的,和最初的parse解析器一样:
进入如该方法:
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
进入这段代码:this.configurationElement(this.parser.evalNode("/mapper"));
,获取到根节点后进入该方法:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
}
}
我们知道每个接口都有与之对应的mapper映射文件,且该文件的namespace就是接口的全限定类名,第一行就是读取namespace,与之对应的还有很多读取配置的方法,我们着重看this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
这句,看他如何解析增删改查标签:
1.6:先拿到增删改查的list标签,进入方法:
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
this.buildStatementFromContext(list, (String)null);
}
先通过刚才配置好的configuration
对象读取数据库的配置信息,再接着进入buildStatementFromContext
方法:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {
XNode context = (XNode)var3.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
观察这个方法,我们发现它将装有增删改查的list集合通过迭代器进行遍历了:
好了,我们有发现了一个解析器:statementParser
,它是解析增删改查标签进行sql解析的,它是怎么进行通过标签来执行sql语句的呢?
1.7:看这个statementParser.parseStatementNode();
方法,顾名思义,通过节点来解析,节点就是我们xml映射文件的增删改查标签,来具体看下这个方法:
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultType = this.context.getStringAttribute("resultType");
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultSetType = this.context.getStringAttribute("resultSetType");
.
.//省略部分代码
.
.
.
.
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
在读取解析完代码后到最后一行:this.builderAssistant.addMappedStatement
这个方法:
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 (this.unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} else {
id = this.applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
this.configuration.addMappedStatement(statement);
return statement;
}
}
这个方法返回的是一个MappedStatement
对象,而这个对象正是封装了增删改查标签中的信息,包括我们配置的SQL,SQL的id,缓存信息,resultMap等,每个标签都有一个该对象封装:
不出意料,这个MappedStatement被设置到了Configuration中:
1.8:到这里整个SqlSessionFactory的任务就结束了,不过需要注意,我们回到第一步:
总结:
整个构建SqlSessionFactory的步骤如下图所示: