本文github地址: 点击
本文gitee 地址: 点击
1. 前言
本章主要分析一下映射文件的解析过程,映射文件指的是mappper.xml 文件
一般我们会在映射文件中配置SQL语句、字段映射关系等等。映射文件中包含<cache>
、<resultMap>
、<sql>
等等二级节点。
下面开始分析吧!
2. 找准目标(映射文件解析的入口在哪?)
映射文件的解析过程是配置文件的解析过程的一部分,MyBatis会在解析配置文件的过程中对映射文件进行解析,具体解析逻辑在mapperElement
方法内。
public class XMLConfigBuilder extends BaseBuilder {
private void parseConfiguration(XNode root) {
.....
// 解析mappers 配置
mapperElement(root.evalNode("mappers"));
}
/**
* 解析mapper映射文件
* @param parent
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取 <package> 节点的name属性
if ("package".equals(child.getName())) {
/*
<mappers>
<package name="xxx.xxx.xxx"/>
</mapper>
*/
// 包名配置
String mapperPackage = child.getStringAttribute("name");
// 从指定包中查找 mapper接口,并根据mapper接口解析映射配置
configuration.addMappers(mapperPackage);
} else {
// 获取 resource/url/class 等属性
/*
<mappers>
<mapper resource="org/mybatis/mappers/UserMapper.xml"/>
</mappers>
*/
// 相对路径
String resource = child.getStringAttribute("resource");
/*
<mappers>
<mapper url="file:///var/mappers/UserMapper.xml"/>
</mappers>
*/
// 绝对路径
String url = child.getStringAttribute("url");
/*
<mappers>
<mapper class="org.mybatis.mappers.UserMapper"/>
</mappers>
*/
//接口信息配置
String mapperClass = child.getStringAttribute("class");
// 如果 resource 不为空 其他两者为空 则从相对路径中加载配置
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配置
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.");
}
}
}
}
}
}
mapperElement 的主要逻辑就是遍历mappers节点中的mapper节点,根据节点值判断该如何加载映射文件信息。
- resource 相对路径进行加载
- url 绝对路径 加载问价
- mapper接口class 加载
- package 扫描包下的所有类
接下来我们看一下 映射文件解析入口parse方法
public class XMLMapperBuilder extends BaseBuilder {
。。。。。
public void parse() {
// 先判断映射文件是否已经被加载过了
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper节点
configurationElement(parser.evalNode("/mapper"));
// 添加资源路径到已解析资源集合中
configuration.addLoadedResource(resource);
// 通过命名空间绑定mapper接口
bindMapperForNamespace();
}
// 处理未完成解析的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
大致三个步骤:
- 解析mapper节点
- 通过命名空间绑定mapper接口
- 处理未完成解析的节点
2.1. 解析映射文件
映射文件包含很多种节点,这里就不举例了,上个简单mapper示例:
<mapper namespace="xxxx.xxx">
<cache/>
<resultMap id="BaseResultMap" type="xxx.xxx">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="age" jdbcType="INT" property="age"/>
</resultMap>
<sql id="table"> user </sql>
<select id="selectById" resultMap="BaseResultMap">
SELECT id, name, age FROM <includ refid="table"/>
WHERE id = #{id}
</select>
......
</mapper>
mapper文件的节点解析逻辑都有封装响应方法,然后由XMLMapperBuilder的configurationElement方法统一调用
public class XMLMapperBuilder extends BaseBuilder {
/**
* 统一调用节点解析配置
*/
private void configurationElement(XNode context) {
try {
// 获取mapper namespace 命名空间
// <mapper namespace="xxx.xxxx.xxxxMapper">
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置namespace到builderAssistant 中
builderAssistant.setCurrentNamespace(namespace);
// 解析 <cache-ref> 节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析 <cache> 节点
cacheElement(context.evalNode("cache"));
// 废弃节点 不做分析
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 <resultMap> 节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 <sql> 节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 <select> <insert> <update> <delete> 等节点
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);
}
}
}
2.1.1. 解析 <cache>
节点
Mybatis 提供一二级缓存,一级缓存为SqlSession级别,默认开启,二级缓存配置在映射文件中,必须要显示配置才可以启用。如果没有特殊配置可以直接
<cache/>
比较复杂的:
<cache eviction="FIFO" flushInterval="60000" size="1024" readOnly="true"/>
- eviction: 缓存策略选择先进先出
- size:缓存容量为1024个对象引用
- flushInterval:缓存每隔60秒刷新一次
- readOnly 写安全,外部修改对象不会影响到缓存内部存储对象
当然还有其他缓存配置了,比如和Ehcache缓存整合,或者自己实现缓存等,这里不细究了。
缓存解析
public class XMLMapperBuilder extends BaseBuilder {
/**
* 缓存解析逻辑
* @param context
*/
private void cacheElement(XNode context) {
if (context != null) {
// 获取<cache>节点的各种属性
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
// 子节点配置
Properties props = context.getChildrenAsProperties();
// 构建缓存对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
}
大致逻辑就是解析属性,然后构建缓存对象,构建方法为 MapperBuilderAssistant.userNewCache
我们来看看 MapperBuilderAssistant.userNewCache 方法吧
public class MapperBuilderAssistant extends BaseBuilder {
/**
* 构造缓存对象
*/
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// 添加缓存到 Configuration 对象中
configuration.addCache(cache);
// 设置 currentCache 遍历,即当前使用的缓存
currentCache = cache;
return cache;
}
}
CacheBuilder.build 负责构建缓存
public class CacheBuilder {
public Cache build() {
// 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
setDefaultImplementations();
// 通过反射创建缓存
Cache cache = newBaseCacheInstance(implementation, id);
// 仅对内置缓存 PerpetualCache 应用装饰器
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
// 遍历装饰器集合,应用装饰器
for (Class<? extends Cache> decorator : decorators) {
// 通过反射创建装饰器实例
cache = newCacheDecoratorInstance(decorator, cache);
// 设置属性值到缓存实例中
setCacheProperties(cache);
}
// 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 应用具有日志功能的缓存装饰器
cache = new LoggingCache(cache);
}
return cache;
}
private void setDefaultImplementations() {
if (implementation == null) {
// 设置默认的缓存实现类
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
// 添加LrcCache装饰器
decorators.add(LruCache.class);
}
}
}
private void setCacheProperties(Cache cache) {
if (properties != null) {
// 为缓存实例生成一个“元信息”实例,forObject 方法调用层次比较深,
// 但最终调用了 MetaClass 的 forClass 方法。
MetaObject metaCache = SystemMetaObject.forObject(cache);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String name = (String) entry.getKey();
String value = (String) entry.getValue();
if (metaCache.hasSetter(name)) {
// 获取 setter 方法的参数类型
Class<?> type = metaCache.getSetterType(name);
// 根据参数类型对属性值进行转换,并将转换后的值
// 通过 setter 方法设置到 Cache 实例
if (String.class == type) {
metaCache.setValue(name, value);
} else if (int.class == type
|| Integer.class == type) {
metaCache.setValue(name, Integer.valueOf(value));
} else if (long.class == type
|| Long.class == type) {
metaCache.setValue(name, Long.valueOf(value));
} else if (short.class == type
|| Short.class == type) {
metaCache.setValue(name, Short.valueOf(value));
} else if (byte.class == type
|| Byte.class == type) {
metaCache.setValue(name, Byte.valueOf(value));
} else if (float.class == type
|| Float.class == type) {
metaCache.setValue(name, Float.valueOf(value));
} else if (boolean.class == type
|| Boolean.class == type) {
metaCache.setValue(name, Boolean.valueOf(value));
} else if (double.class == type
|| Double.class == type) {
metaCache.setValue(name, Double.valueOf(value));
} else {
throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
}
}
}
}
// 如果缓存类实现了 InitializingObject 接口,
// 则调用 initialize 方法执行初始化逻辑
if (InitializingObject.class.isAssignableFrom(cache.getClass())) {
try {
((InitializingObject) cache).initialize();
} catch (Exception e) {
throw new CacheException("Failed cache initialization for '"
+ cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
}
}
}
private Cache setStandardDecorators(Cache cache) {
try {
// 创建“元信息”对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
// 设置 size 属性,
metaCache.setValue("size", size);
}
if (clearInterval != null) {
// clearInterval 不为空,应用 ScheduledCache 装饰器
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
// readWrite 为 true,应用 SerializedCache 装饰器
cache = new SerializedCache(cache);
}
/*
* 应用 LoggingCache,SynchronizedCache 装饰器, * 使原缓存具备打印日志和线程同步的能力
*/
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
// blocking 为 true,应用 BlockingCache 装饰器
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
}
- 设置默认的缓存类型及装饰器
- 应用装饰器到 PerpetualCache 对象上
- 应用标准装饰器
- 对非 LoggingCache 类型的缓存应用 LoggingCache 装饰器
2.1.2. 解析<cache-ref>
节点
Mybatis 二级缓存可以共用,通过 <cache-ref>
来配置参照缓存
大致逻辑已备注在代码中了 不做详细介绍了
public class XMLMapperBuilder extends BaseBuilder {
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
// cacheRefResolver示例
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 解析参照缓存
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
// 捕捉IncompleteElementException 异常,并将 cacheRefResolver // 存入到 Configuration 的 incompleteCacheRefs 集合中
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
}
public class CacheRefResolver {
private final MapperBuilderAssistant assistant;
private final String cacheRefNamespace;
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
this.assistant = assistant;
this.cacheRefNamespace = cacheRefNamespace;
}
public Cache resolveCacheRef() {
// 调用 builderAssistant 的 useNewCache(namespace) 方法
return assistant.useCacheRef(cacheRefNamespace);
}
}
public class MapperBuilderAssistant extends BaseBuilder {
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
// 根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例
Cache cache = configuration.getCache(namespace);
/*
若未查找到缓存实例,此处抛出异常。这里存在两种情况导致未查找到 cache实例,分别如下:
1.使用者在 <cache-ref> 中配置了一个不存在的命名空间, 导致无法找到 cache 实例
2.使用者所引用的缓存实例还未创建
*/
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
// 设置 cache 为当前使用缓存
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
}
2.1.3. <resultMap>
节点
public class XMLMapperBuilder extends BaseBuilder {
private void resultMapElements(List<XNode> list) {
// 遍历 <resultMap>节点列表
for (XNode resultMapNode : list) {
try {
// 解析resultMap节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取type属性
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 解析type属性对应的class类型
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
// 获取并遍历 <resultMap> 的子节点列表
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
// 解析 constructor 节点,并生成相应的 ResultMapping
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 解析 discriminator 节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
// 添加 ID 到 flags 集合中
flags.add(ResultFlag.ID);
}
// 解析 id 和 property 节点,并生成相应的 ResultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 获取 id 属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 获取extends属性
String extend = resultMapNode.getStringAttribute("extends");
// 获取autoMapping属性
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 根据前面获取到的信息构建 ResultMap 对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
/*
* 如果发生 IncompleteElementException 异常,
* 这里将 resultMapResolver 添加到 incompleteResultMaps 集合中
* */
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
}
- 获取
<resultMap>
节点的各种属性 - 遍历
<resultMap>
的子节点,并根据子节点名称执行相应的解析逻辑 - 构建 ResultMap 对象
- 若构建过程中发生异常,则将 resultMapResolver 添加到incompleteResultMaps 集合中
2.1.4. 解析<sql>
节点
入口:
public class XMLMapperBuilder extends BaseBuilder {
/**
* 统一调用节点解析配置
*/
private void configurationElement(XNode context) {
try {
// 解析 <sql> 节点
sqlElement(context.evalNodes("/mapper/sql"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
有兴趣的可以自己跟踪源码看一下
2.1.5. 解析SQL语句节点
<select> <insert> <update> <delete>
节点的解析
具体跟踪源码吧 内容较多以后可能会完善
2.2. Mapper接口绑定
- 获取命名空间 根据命名空间解析mapper类型
- 将type和mapperProxyFactory实例存入konwnMappers中
- 解析注解中得信息