MyBatis流程(第一阶段)
MyBatis流程(第一阶段)
作者:IT王小二
初始化阶段:读取 XML 配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作,这么简单的几行代码,背后到底做了多少事情呢。
// --------------------第一阶段---------------------------
// 1.读取mybatis配置文件创建SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
MyBatis 在加载配置信息是大量使用了 建造者模式,让复杂的对象构建起来更简单,流式编程(链式编程)是建造者模式比较好的体现。
一、配置加载的几个重要的类
- BaseBuilder:所有解析器的父类,包含配置文件实例,为解析文件提供的一些通用的方法。
- XMLConfigBuilder: 主要负责解析 mybatis-config.xml。
- XMLMapperBuilder: 主要负责解析映射配置 Mapper.xml 文件。
- XMLStatementBuilder: 主要负责解析映射配置文件中的 SQL 节点。
Configuration:
实例化并初始化 Configuration 对象是第一个阶段的最终目的,将 xml 文件中的信息解析成这个对象对应的属性。
- 这个类的属性比较多,大概有40多个属性。
- XMLConfigBuilder 解析相对 XMLMapperBuilder和XMLStatementBuilder 来说相对简单,所以直接将数据填充到 Configuration 。
- 而 XMLMapperBuilder和XMLStatementBuilder 解析的过程及数据比较复杂,所以会通过 MapperBuilderAssistant 来将 XMLMapperBuilder和XMLStatementBuilder 解析出来的内容设置给 Configuration 。
其中几个关键属性:
- MapperRegistry:mapper 接口动态代理工厂类的注册中心。在 MyBatis 中,通过mapperProxy 实现 InvocationHandler 接口,MapperProxyFactory 用于生成动态代理的实例对象。
- ResultMap:用于解析 mapper.xml 文件中的 resultMap 节点,使用 ResultMapping 来封装id,result 等子元素。
- MappedStatement:用于存储 mapper.xml 文件中的 select、insert、update 和 delete 节点,同时还包含了这些节点的很多重要属性。
- SqlSource:用于创建 BoundSql,mapper.xml 文件中的 sql 语句会被解析成 BoundSql 对象,经过解析 BoundSql 包含的语句最终仅仅包含占位符(?),可以直接提交给数据库执行。
需要特别注意的是 Configuration 对象在 MyBatis 中是单例的,生命周期是应用级的,换句话说只要 MyBatis 运行 Configuration 对象就会独一无二的存在。
在 MyBatis 中仅在 org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder
中有实例化 Configuration 对象的代码,如下:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 仅在此处实例化一个Configuration
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
二、配置加载过程
可以把第一阶段加载过程分为4个步骤。
1. 第一步
通过 SqlSessionFactoryBuilder 建造 SqlSessionFactory,并创建 XMLConfigBuilder 对象读取 MyBatis 核心配置文件,方法:org.apache.ibatis.session.SqlSessionFactoryBuilder#build
。
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件得到configuration对象,并返回SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 省略其他代码
}
2. 第二步
进入 XMLConfigBuilder 的 parse 方法,然后调用核心配置方法 parseConfiguration 方法,对 MyBatis 核心配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象。在 XMLConfigBuilder 的mapperElement()方法中通过 XMLMapperBuilder 读取所有 mapper.xml 文件。
具体方法:org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
public class XMLConfigBuilder extends BaseBuilder {
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 {
// issue #117 read properties first
// 解析<properties>节点
propertiesElement(root.evalNode("properties"));
// 解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins>节点
pluginElement(root.evalNode("plugins"));
// 解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
// 解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 将settings填充到configuration
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析<environments>节点
environmentsElement(root.evalNode("environments"));
// 解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析<mappers>节点,并且生成XMLMapperBuilder对象
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
/**
* 解析<mappers>节点,并且生成XMLMapperBuilder对象,同时调用解析mapper文件
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理mapper子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// package子节点
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 获取<mapper>节点的resource、url或mClass属性这三个属性互斥
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);
// 加载mapper文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 如果url不为空
ErrorContext.instance().resource(url);
// 加载mapper文件
InputStream inputStream = Resources.getUrlAsStream(url);
// 实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 如果class不为空
// 加载class对象
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 向代理中心注册mapper
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
// 省略其他代码
}
3. 第三步
通过第二步,可以看到 mapperParser.parse() 这个调用,点击去看一下,看到了核心配置方法 XMLMapperBuilder 的核心方法为 configurationElement,该方法对 mapper.xml 配置文件的各个元素进行解析,读取元素信息后填充到 configuration 对象。
具体方法:org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 判断是否已经加载该配置文件
if (!configuration.isResourceLoaded(resource)) {
// 处理mapper节点
configurationElement(parser.evalNode("/mapper"));
// 将mapper文件添加到configuration.loadedResources中
configuration.addLoadedResource(resource);
// 注册mapper接口
bindMapperForNamespace();
}
// 处理解析失败的ResultMap节点
parsePendingResultMaps();
// 处理解析失败的CacheRef节点
parsePendingCacheRefs();
// 处理解析失败的Sql语句节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置builderAssistant的namespace属性
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref节点(二级缓存的引用空间)
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache节点
cacheElement(context.evalNode("cache"));
// 解析parameterMap节点(已废弃)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql节点<sql></sql>
sqlElement(context.evalNodes("/mapper/sql"));
// 解析select、insert、update、delete节点,这里面创建 XMLStatmentBuilder
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);
}
}
/**
* 这个方法解开了之前二级缓存装饰器的设置谜题
*/
private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取cache节点的type属性,默认为PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
//找到type对应的cache接口的实现
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//读取eviction属性,即缓存的淘汰策略,默认LRU
String eviction = context.getStringAttribute("eviction", "LRU");
//根据eviction属性,找到装饰器
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//读取flushInterval属性,即缓存的刷新周期
Long flushInterval = context.getLongAttribute("flushInterval");
//读取size属性,即缓存的容量大小
Integer size = context.getIntAttribute("size");
//读取readOnly属性,即缓存的是否只读
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
//读取blocking属性,即缓存的是否阻塞
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//通过builderAssistant创建缓存对象,并添加至configuration
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
/**
* resultMap的解析
*/
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取resultmap节点的id属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 获取resultmap节点的type属性
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 获取resultmap节点的extends属性,描述继承关系
String extend = resultMapNode.getStringAttribute("extends");
// 获取resultmap节点的autoMapping属性,是否开启自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 从别名注册中心获取entity的class对象
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 记录子节点中的映射结果集合
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// 从xml文件中获取当前resultmap中的所有子节点,并开始遍历
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
// 处理<constructor>节点
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 处理<discriminator>节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 处理<id> <result> <association> <collection>节点
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
// 如果是id节点,向flags中添加元素
flags.add(ResultFlag.ID);
}
// 创建ResultMapping对象并加入resultMappings集合中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 实例化resultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 通过resultMap解析器实例化resultMap并将其注册到configuration对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
// 省略其他代码
}
注意点:
resultMapElements(List<XNode>)
方法用于解析 resultMap 节点,解析完之后数据保存在 configuration 对象的 resultMaps 属性中。- XMLMapperBuilder 中在实例化二级缓存(见 cacheElement(XNode))、实例化 resultMap(见
resultMapElements(List<XNode>)
)过程中都使用了建造者模式,而且是建造者模式的典型应用。 - XMLMapperBuilder 和 XMLMapperStatmentBuilder 有自己的助手 MapperBuilderAssistant。XMLMapperBuilder 和 XMLMapperStatmentBuilder 负责解析读取配置文件里面的信息,MapperBuilderAssistant 负责将信息填充到 configuration。将文件解析和数据的填充的工作分离在不同的类中,符合单一职责原则。
- 在
buildStatementFromContext(List<XNode>)
方法中,创建 XMLStatmentBuilder 解析Mapper.xml 中 select、insert、update、delete 节点。
4. 第四步
在 XMLStatmentBuilder 的 parseStatementNode 方法中,对 Mapper.xml 中 select、 insert、update、delete 节点进行解析,并调用 MapperBuilderAssistant 负责将信息填充到 configuration 。
在理解 parseStatementNod()方法之前,有必要了解 MappedStatement,这个类用于封装 select、insert、update、delete 节点的信息。
具体方法:org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public class XMLStatementBuilder extends BaseBuilder {
public void parseStatementNode() {
// 获取sql节点的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 根据sql节点的名称获取SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
String nodeName = context.getNode().getNodeName();
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
// 在解析sql语句之前先解析<include>节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 获取节点parameterType
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//在解析sql语句之前,处理<selectKey>子节点,并在xml节点中删除
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 根据<selectKey>获取对应的SelectKeyGenerator的id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 获取一堆节点参数
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 通过builderAssistant实例化MappedStatement,并注册至configuration对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// 省略其他代码
}
看完了流程,发现其实是一个无限套娃,然后将解析结果设置到 Configuration 对象的过程。