通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis主文件加载

前话

前文主要讲解了Mybatis的主文件加载方式,本文则分析不使用主文件加载方式,即SqlSessionFactoryBean对象不配置configLocation属性而配置mapperLocations属性

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations" value="classpath:com/du/wx/resources/mapper/*.xml" />
</bean>

其中每个mapper文件均类似如下样例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.du.wx.mapper.joggle.ActivityBookingDao">
<insert id="addBookingInfo">
insert into booking_activity_infos(uid,activityid,isbooked)
values(#{uid},#{activityid},#{isbooked})
</insert> <select id="queryCountBookingByAct" resultType="int">
select count(*) from booking_activity_infos
where
activityid=#{0}
</select> <select id="queryCountBookingByUser" resultType="int">
select count(*) from booking_activity_infos
where
uid=#{0}
</select> <select id="queryUsersByAct" resultType="BaseUser">
select a.uid,a.openid,a.nickname,a.phone,a.email,a.address
from booking_activity_infos b
right join
base_user a on(b.uid=a.uid)
where b.activityid=#{0} and b.isbooked=1
</select> <select id="queryActByUser" resultType="Activity">
select a.id,a.title,a.content,a.url,a.address,a.time,a.type,a.status,a.limit,a.people
from activity a
left join
booking_activity_infos b
on(a.id=b.activityid)
where b.uid=#{0} and b.isbooked=1
</select> <select id="queryActBooking" resultType="ActivityBooking">
select * from booking_activity_infos
where uid=#{0} and activityid=#{1}
</select> <update id="updateBooking">
update booking_activity_infos
set
isbooked=#{2}
where uid=#{0} and activityid=#{1}
</update>
</mapper>

SqlSessionFactoryBean读取mapper配置文件集合

代码片段如下

	if (!isEmpty(this.mapperLocations)) {
//具体的如何从string转为Resource[],属于spring的基础操作了,有兴趣的读者可自行查阅
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
} try {
//对扫描包及其子包下的每个mapper配置文件进行解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
} if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}

主要是遍历mapper集合,然后对每个XML文件都通过XMLMapperBuilder对象来进行解析。这在前文也有提及

XMLMapperBuilder

笔者直接去观察其parse()方法,代码如下

public void parse() {
//对每个xml资源只加载一次
if (!configuration.isResourceLoaded(resource)) {
//解析xml配置,其中配置的根节点必须为mapper
configurationElement(parser.evalNode("/mapper"));
//已加载
configuration.addLoadedResource(resource);
//绑定mapper的工作区间
bindMapperForNamespace();
} // 弥补措施
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}

紧接着观察XMLMapperBuilder#configurationElement()方法,其来解析mapper文件

  private void configurationElement(XNode context) {
try {
//表明mapper根节点的namespace属性是必须的,且不为空
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置工作区间
builderAssistant.setCurrentNamespace(namespace);
//解析相应的属性
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析<parameterMap>节点集合
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析<resultMap>节点集合
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点集合
sqlElement(context.evalNodes("/mapper/sql"));
//创建MappedStatement,这里与注解方式的加载方式还是类似的
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

1.mapper文件的namespace属性不可或缺,否则会抛异常

2.mapper文件的parameterMap/resultMap/sql节点一般都是用来充当模板使用的

3.mapper文件对应的数据库的增删改查节点分别为insert|delete|update|select

笔者稍微对上述的节点作下简单的浏览

resultMap

解析mapper节点下的resultMap节点,相关代码如下

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//读取id属性,最好配置以免不必要的错误
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//优先级为type>ofType>resultType>javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType")))); String extend = resultMapNode.getStringAttribute("extends");
//是否开启自动映射,默认值为unset
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type); //<discriminator><case /><case/></discriminator>根据结果值进行结果类型的映射,类似java的switch-case语法
Discriminator discriminator = null;
//ResultMap节点信息转化为ResultMapping集合
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//<resultMap>节点下<constructor>节点处理
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//<resultMap>节点下<discriminator>节点处理
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
//<id>/<result>/<collection>/<association>节点的解析
ArrayList<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 {
//组装成ResultMap对象保存到Configuration对象的私有集合变量resultMaps
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}

具体的属性就不展开了,查阅相关的官方文档即可

sql

解析mapper节点下的sql节点。主要作用是将每个sql节点对象都保存到Configuration对象中的sqlFragments属性中(HashMap)。

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
//sql节点的databaseId属性
String databaseId = context.getStringAttribute("databaseId");
//sql节点的id属性,id=${namespace}+"."+id
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
//true的前提是主配置文件指定了databaseId属性或者主配置和sql节点的databaseId属性均不存在,但sql节点的id属性存在
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
}
}

sql节点在配置id属性时,可简单的配置别名而不用带上namespace前缀,代码会自动校验进行拼装~~

CRUD节点解析

即解析select/update/delete/insert节点对应的信息,笔者此处关注XMLStatementBuilder#parseStatementNode()方法的片段代码

	//节点上支持的常见属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
...
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
...
// Include Fragments before parsing 导入<include>标签内容,其内部可以含有<if>/<where>/<set>等标签
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them.导入<selectKey>标签内容
processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 如何解析sql语句?放置下一章节讲解
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); ...
//创建MappedStatement对象,保存在Configuration的mappedStatement集合属性中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

代码其实很长,不过跟前文的注解方式的解析是类似的,最终都是生成MappedStatement对象。

而其会保存在org.apache.ibatis.session.Configuration对象中~~~~

总结

1.不管是通过注解模式还是XML文件模式,都会生成MappedStatement对象保存到Configuration对象中

2.每个select|update|insert|delete标签均会被解析为单个MappedStatement对象,其中唯一ID为${namespace}.${id}

3.Spring官方以及笔者都建议通过此方式来加载指定的XML文件,但是细心的笔者发现这其实并没有与DAO接口关联起来,看来还是得借助org.mybatis.spring.mapper.MapperScannerConfigurer的力量

Spring mybatis源码篇章-Mybatis的XML文件加载的更多相关文章

  1. Spring mybatis源码篇章-Mybatis主文件加载

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 前话 本文承接前文的内容继续往下扩展,通过Spring与Mybatis的 ...

  2. Spring mybatis源码篇章-sql mapper配置文件绑定mapper class类

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 背景知识 MappedStatement是mybatis操作sql ...

  3. Spring mybatis源码篇章-MybatisDAO文件解析(二)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(一) 默认加载mybatis主文件方式 XMLConfigBuilder ...

  4. Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-MybatisDAO文件解析(二) 首先了解下sql mapper的动态sql语法 具体的动态sql的 ...

  5. Spring mybatis源码篇章-MapperScannerConfigurer关联dao接口

    前言:Spring针对Mybatis的XML方式的加载MappedStatement,通过引入MapperScannerConfigurer扫描类来关联相应的dao接口以供Service层调用.承接前 ...

  6. Spring mybatis源码篇章-动态SQL基础语法以及原理

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis的XML文件加载 前话 前文通过Spring中配置mapperLocations属性来进行对m ...

  7. Spring mybatis源码篇章-MybatisDAO文件解析(一)

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-SqlSessionFactory 加载指定的mybatis主文件 Mybatis模板文件,其中的属性 ...

  8. Spring mybatis源码篇章-NodeHandler实现类具体解析保存Dynamic sql节点信息

    前言:通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource SqlNode接口类 publi ...

  9. Spring mybatis源码篇章-动态SQL节点源码深入

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-动态SQL基础语法以及原理 前话 前文描述到通过mybatis默认的解析驱动类org.apache.ibat ...

随机推荐

  1. 004_STM32程序移植之_SHTXX

    1. 测试环境:STM32C8T6 2. 测试模块:DS1302时钟模块 3. 测试接口: SHTXX土壤温湿度: VCC------------------3.3V GND------------- ...

  2. seq2seq聊天模型(二)——Scheduled Sampling

    使用典型seq2seq模型,得到的结果欠佳,怎么解决 结果欠佳原因在这里 在训练阶段的decoder,是将目标样本["吃","兰州","拉面" ...

  3. Jenkins+Ant+Git+Jmeter接口自动化

    一.服务器分别安装JKD.Jenkins.Ant.Git.Jmeter 1.JKD安装参考:https://www.cnblogs.com/xiaoxitest/p/6168045.html 2.Je ...

  4. CF1208B

    CF1208B 题意: 给出n个数字,找出最小的一端连续区间进行删除操作,使其剩余元素不含重复元素,求要删除的最小区间长度 解法: 删除子段后,前缀和后缀保持不变,可能长度为0.让我们修复不包含任何重 ...

  5. SQL Server Management Studio 清除用户名和密码

    SQL Server Management Studio 2018 delete the file C:\Users\%username%\AppData\Roaming\Microsoft\SQL  ...

  6. matplotlib画图报错This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.

    之前用以下代码将实验结果用matplotlib show出来 plt.plot(np.arange(len(aver_reward_list)), aver_reward_list) plt.ylab ...

  7. 性能优化 | JVM性能调优篇——来自阿里P7的经验总结

    VM 调优概述: 性能定义: 吞吐量 - 指不考虑 GC 引起的停顿时间或内存消耗,垃圾收集器能支撑应用达到的最高性能指标. 延迟 - 其度量标准是缩短由于垃圾啊收集引起的停顿时间或者完全消除因垃圾收 ...

  8. Java同步数据结构之LinkedTransferQueue

    前言 LinkedTransferQueue是Java并发包中最强大的基于链表的无界FIFO阻塞传输队列.从JDK7开始出现,Doug Lea说LinkedTransferQueue是Concurre ...

  9. 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_16-异常处理-可预知异常处理-自定义异常类型和抛出类

    在common工程创建捕获异常的类:CustomException Runtime叫做运行异常.在代码中抛出的话 对我们的代码没有可侵入性 如果在代码上抛出 如果改成Exception 这时候就会有错 ...

  10. java -- SSM配置完成后,能访问jsp文件不能访问html文件,报错解析

    SSM配置完成后,能访问jsp文件不能访问html文件,报错解析 在确保路径没有任何问题的,情况下,相同的页面,jsp能够正常访问,html却不能正常访问(404). 解决方法: 在web.xml中添 ...