目录
一、mybatis核心流程
我们将mybatis启动分为三个阶段,分别为
初始化阶段:该阶段主要工作,将核心配置文件mybatis-config.xml和所有的mapper.xml进行读取,并保存到配置对象Configuration中
代理阶段:该阶段主要工作,对mapper接口进行增强,使我们能够面向接口编程
数据读写阶段:该阶段主要工作,解析sql并执行
二、初始化阶段
配置文件mybatis-config.xml比较简单,很容易解析
mapper.xml相对来说就很复杂,涉及映射关系,sql语句等等
在这里mybatis使用了建造者模式来完成初始化阶段
2.1 建造者模式(Builder Pattern)概念
使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
2.2 四个角色
Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建
ConcreteBuilder:实现Builder接口,针对不同的业务逻辑,具体化复杂对象的各部分的创建。在建造过程完成后,提供产品的实例
Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建
Product:要创建的复杂对象
2.3 使用场景
需要生成的对象具有复杂的内部结构,实例化对象时要屏蔽掉对象内部的细节,让上层代码与复杂对象的实例化过程解耦,可以使用建造者模式;简而言之,如果“遇到多个构造器参数时要考虑用构建器”
一个对象的实例化是依赖各个组件的产生以及装配顺序,关注的是一步一步地组装出目标对象,可以使用建造器模式
2.4 建造者模式和工厂模式的区别
1)对象复杂度
建造者建造的对象更加复杂,是一个复合产品,它由各个部件复合而成,部件不同产品对象不同,生成的产品粒度细;
在工厂方法模式里,我们关注的是一个产品整体,无须关心产品的各部分是如何创建出来的;
2)客户端参与程度
建造者模式,导演对象参与了产品的创建,决定了产品的类型和内容,参与度高;适合实例化对象时属性变化
频繁的场景;
工厂模式,客户端对产品的创建过程参与度低,对象实例化时属性值相对比较固定
三、Mybatis建造者类图
XMLConfigBuilder: 负责解析mybatis-config.xml
XMLMapperBuilder: 负责解析mapper.xml文件中的映射关系
XMLStatementBuilder: 负责解析mapper.xml文件中的SQL语句
就是通过上面三个Builder将mybatis的核心配置文件以及所有的Mapper.xml文件进行解析,并将信息封装到配置对象Configuration类(Configuration是单例的,生命周期是应用级)中。
四、解析流程
我们开始解析源码,之前我们写过
@Before
public void init() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}
我们调用了SqlSessionFactoryBuilder()的build(inputStream)
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
Build方法中就创建了XMLConfigBuilder对象,同时调用了它的parse()
parseConfiguration(parser.evalNode("/configuration"));
Configuration就是我们mybatis-config.xml配置文件的根节点
private void parseConfiguration(XNode root) {
try {
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);
}
}
从根节点开始解析,我们可以看到properties,typeAliases,plugins等都是mybatis-config.xml配置文件的节点,每个节点使用一个方法去解析,解析完后,将信息保存到configuration配置对象中区,以propertiesElement(root.evalNode("properties"));为例:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
其他节点比较类似,就不一一介绍,我们来看最后一个mapper节点
mapperElement(root.evalNode("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.");
}
}
}
}
}
我们可以看出,该方法中对每一个mapper文件进行遍历,同时进行了一些判断,并创建XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
调用parse方法去解析mapper.xml文件中的映射关系
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement方法就是用来解析mapper.xml
源码如下
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. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
其中resultMapElements(context.evalNodes("/mapper/resultMap"));就是解析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<>();
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<>();
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;
}
}
最终通过上述方法完成对mapper.xml文件中映射关系的解析,并保存到Configuration配置对象中
然后通过buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
在其中创建
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
使用XMLStatementBuilder完成对sql语句的解析
最终都保存到Configuration配置对象中
总结:mybatis的初始化阶段就是解析配置文件
通过三个builder进行解析,三个解析器负责不同的部分,解析到那部分,使用建造者模式创建该部分对象,最终都保存到Configuration配置对象中,完成初始化阶段