Spring整合Mybatis源码剖析(三)-xml文件在哪被扫描解析的(2-解析过程)

最后

很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。

我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。

不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

this.sqlSessionFactory = buildSqlSessionFactory();

}

前面有解析Mybatis-config.xml配置文件的,这里不跟了,]代码太多了,省略了

重点看下怎么解析我们自定义的xml的

循环mapperLocation(xml文件路径),拿到就是单个xml文件

image-20210726174430767

构建XMLMapperBuilder 去解析

//…省略

if (this.mapperLocations != null) {

if (this.mapperLocations.length == 0) {

LOGGER.warn(() -> “Property ‘mapperLocations’ was specified but matching resources are not found.”);

} else {

for (Resource mapperLocation : this.mapperLocations) {

if (mapperLocation == null) {

continue;

}

try {

/**

  • 真正的循环我们的mapper.xml文件

*/

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),

targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

xmlMapperBuilder.parse();

} catch (Exception e) {

throw new NestedIOException(“Failed to parse mapping resource: '” + mapperLocation + “'”, e);

} finally {

ErrorContext.instance().reset();

}

LOGGER.debug(() -> “Parsed mapper file: '” + mapperLocation + “'”);

}

}

}

进入xmlMapperBuilder.parse();

public void parse() {

/**

  • 判断当前的Mapper是否被加载过

*/

if (!configuration.isResourceLoaded(resource)) {

/**

  • 真正的解析我们的

*/

configurationElement(parser.evalNode(“/mapper”));

/**

  • 把资源保存到我们Configuration中

*/

configuration.addLoadedResource(resource);

bindMapperForNamespace();

}

parsePendingResultMaps();

parsePendingCacheRefs();

parsePendingStatements();

}

进入configurationElement(parser.evalNode("/mapper"));

开始解析xml文件,里面的属性都会解析(nameSpace,resultMap,mapper…)

private void configurationElement(XNode context) {

try {

/**

  • 解析我们的namespace属性

*/

String namespace = context.getStringAttribute(“namespace”);

if (namespace == null || namespace.equals(“”)) {

throw new BuilderException(“Mapper’s namespace cannot be empty”);

}

/**

  • 保存我们当前的namespace 并且判断接口完全类名==namespace

*/

builderAssistant.setCurrentNamespace(namespace);

/**

  • 解析我们的缓存引用

  • 说明我当前的缓存引用和DeptMapper的缓存引用一致

解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>

异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs

*/

cacheRefElement(context.evalNode(“cache-ref”));

/**

  • 解析我们的cache节点

解析到:org.apache.ibatis.session.Configuration#caches

org.apache.ibatis.builder.MapperBuilderAssistant#currentCache

*/

cacheElement(context.evalNode(“cache”));

/**

  • 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)

*/

parameterMapElement(context.evalNodes(“/mapper/parameterMap”));

/**

  • 解析我们的resultMap节点

  • 解析到:org.apache.ibatis.session.Configuration#resultMaps

  • 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps

*/

resultMapElements(context.evalNodes(“/mapper/resultMap”));

/**

  • 解析我们通过sql节点

  • 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments

  • 其实等于 org.apache.ibatis.session.Configuration#sqlFragments

  • 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了

*/

sqlElement(context.evalNodes(“/mapper/sql”));

/**

  • 解析我们的select | insert |update |delete节点

  • 解析到org.apache.ibatis.session.Configuration#mappedStatements

*/

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);

}

}

重点看下我们自定义的增删改查节点

进入buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

private void buildStatementFromContext(List list) {

/**

  • 判断有没有配置数据库厂商ID

*/

if (configuration.getDatabaseId() != null) {

buildStatementFromContext(list, configuration.getDatabaseId());

}

buildStatementFromContext(list, null);

}

进入buildStatementFromContext(list, null);

循环所有的增删改查节点

image-20210726174922341

private void buildStatementFromContext(List list, String requiredDatabaseId) {

/**

  • 循环我们的select|delte|insert|update节点

*/

for (XNode context : list) {

/**

  • 创建一个xmlStatement的构建器对象

*/

final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);

try {

statementParser.parseStatementNode();

} catch (IncompleteElementException e) {

configuration.addIncompleteStatement(statementParser);

}

}

}

构建XMLStatementBuilder开始解析

public void parseStatementNode() {

/**

  • 我们的insert|delte|update|select 语句的sqlId

*/

String id = context.getStringAttribute(“id”);

/**

  • 判断我们的insert|delte|update|select 节点是否配置了

  • 数据库厂商标注

*/

String databaseId = context.getStringAttribute(“databaseId”);

/**

  • 匹配当前的数据库厂商id是否匹配当前数据源的厂商id

*/

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {

return;

}

/**

  • 获得节点名称:select|insert|update|delete

*/

String nodeName = context.getNode().getNodeName();

/**

  • 根据nodeName 获得 SqlCommandType枚举

*/

SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

/**

  • 判断是不是select语句节点

*/

boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

/**

  • 获取flushCache属性

  • 默认值为isSelect的反值:查询:默认flushCache=false 增删改:默认flushCache=true

*/

boolean flushCache = context.getBooleanAttribute(“flushCache”, !isSelect);

/**

  • 获取useCache属性

  • 默认值为isSelect:查询:默认useCache=true 增删改:默认useCache=false

*/

boolean useCache = context.getBooleanAttribute(“useCache”, isSelect);

/**

  • resultOrdered: 是否需要处理嵌套查询结果 group by (使用极少)

  • 可以将比如 30条数据的三组数据 组成一个嵌套的查询结果

*/

boolean resultOrdered = context.getBooleanAttribute(“resultOrdered”, false);

/**

  • 解析我们的sql公用片段

  • <select id="qryEmployeeById" resultType="Employee" parameterType="int">
    

employee where id=#{id}

将 解析成sql语句 放在Node的子节点中

*/

// Include Fragments before parsing

XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);

includeParser.applyIncludes(context.getNode());

/**

  • 解析我们sql节点的参数类型

*/

String parameterType = context.getStringAttribute(“parameterType”);

// 把参数类型字符串转化为class

Class<?> parameterTypeClass = resolveClass(parameterType);

/**

  • 查看sql是否支撑自定义语言

*/

String lang = context.getStringAttribute(“lang”);

/**

  • 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver

*/

LanguageDriver langDriver = getLanguageDriver(lang);

// Parse selectKey after includes and remove them.

/**

  • 解析我们<insert 语句的的selectKey节点, 还记得吧,一般在oracle里面设置自增id

*/

processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: and were parsed and removed)

/**

  • 我们insert语句 用于主键生成组件

*/

KeyGenerator keyGenerator;

/**

  • selectById!selectKey

  • id+!selectKey

*/

String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;

/**

  • 把我们的命名空间拼接到keyStatementId中

  • com.cheng.mapper.User.saveUser!selectKey

*/

keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);

/**

*

*判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象

*/

if (configuration.hasKeyGenerator(keyStatementId)) {

keyGenerator = configuration.getKeyGenerator(keyStatementId);

} else {

/**

  • 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,

  • 否者就看我们的mybatis-config.xml配置文件中是配置了

  • 默认是false

  • 并且判断sql操作类型是否为insert

  • 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE

  • 否则就是NoKeyGenerator.INSTANCE

*/

keyGenerator = context.getBooleanAttribute(“useGeneratedKeys”,

configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))

? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;

}

/**

  • 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的

  • sql脚本对象 . 解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析

*/

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

/**

  • STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED

*/

StatementType statementType = StatementType.valueOf(context.getStringAttribute(“statementType”, StatementType.PREPARED.toString()));

/**

  • 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)

*/

Integer fetchSize = context.getIntAttribute(“fetchSize”);

/**

  • 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。

*/

Integer timeout = context.getIntAttribute(“timeout”);

/**

  • 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置

*/

String parameterMap = context.getStringAttribute(“parameterMap”);

/**

  • 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。

  • 可以使用 resultType 或 resultMap,但不能同时使用

*/

String resultType = context.getStringAttribute(“resultType”);

/**解析我们查询结果集返回的类型 */

Class<?> resultTypeClass = resolveClass(resultType);

/**

  • 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。

  • 可以使用 resultMap 或 resultType,但不能同时使用。

*/

String resultMap = context.getStringAttribute(“resultMap”);

String resultSetType = context.getStringAttribute(“resultSetType”);

ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

if (resultSetTypeEnum == null) {

resultSetTypeEnum = configuration.getDefaultResultSetType();

}

/**

  • 解析 keyProperty keyColumn 仅适用于 insert 和 update

*/

String keyProperty = context.getStringAttribute(“keyProperty”);

String keyColumn = context.getStringAttribute(“keyColumn”);

String resultSets = context.getStringAttribute(“resultSets”);

/**

  • 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象

*/

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,

fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,

resultSetTypeEnum, flushCache, useCache, resultOrdered,

最后

小编精心为大家准备了一手资料

以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

【附】架构书籍

  1. BAT面试的20道高频数据库问题解析
  2. Java面试宝典
  3. Netty实战
  4. 算法

BATJ面试要点及Java架构师进阶资料

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

rMap, parameterTypeClass, resultMap, resultTypeClass,

resultSetTypeEnum, flushCache, useCache, resultOrdered,

最后

小编精心为大家准备了一手资料

[外链图片转存中…(img-W2OaiPAN-1715471081046)]

[外链图片转存中…(img-jbEx0iBk-1715471081047)]

以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

【附】架构书籍

  1. BAT面试的20道高频数据库问题解析
  2. Java面试宝典
  3. Netty实战
  4. 算法

[外链图片转存中…(img-D2PZrEz4-1715471081047)]

BATJ面试要点及Java架构师进阶资料

[外链图片转存中…(img-Y1v9Uag0-1715471081047)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 14
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值