学习源码过程中随手记录的笔记,仅供参考,有问题欢迎指出交流
可能比较枯燥,耐点心,但是弄懂了,必能知其然而知其所以然
学习源码建议亲手debug调试
使用的源码版本
mybatis版本3.5.3
spring版本5.2.0
上篇说到具体的解析xml文件过程会在
XMLMapperBuilder#parse()
去解析
下面一起来探究下具体的解析过程
由于各个项目配置的sqlFactoryBean方式不一样或者有些配置不一样或者mtbatis版本原因,加载时机可能不一样,但是核心解析方法都是大差不差的
回顾下加载时机
SqlSessionFactoryBean#afterPropertiesSet()
进入buildSqlSessionFactory方法
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
前面有解析Mybatis-config.xml配置文件的,这里不跟了,]代码太多了,省略了
重点看下怎么解析我们自定义的xml的
循环mapperLocation(xml文件路径),拿到就是单个xml文件
构建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)) {
/**
* 真正的解析我们的 <mapper namespace="com.cheng.mapper.UserMapper">
*
*/
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属性
* <mapper namespace="com.cheng.mapper.UserMapper">
*/
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的缓存引用一致
* <cache-ref namespace="com.cheng.mapper.UserMapper"></cache-ref>
解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 解析我们的cache节点
* <cache type="org.mybatis.caches.ehcache.EhcacheCache"></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<XNode> list) {
/**
* 判断有没有配置数据库厂商ID
*/
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
进入buildStatementFromContext(list, null);
循环所有的增删改查节点
private void buildStatementFromContext(List<XNode> 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">
<include refid="selectInfo"></include>
employee where id=#{id}
</select>
将 <include refid="selectInfo"></include> 解析成sql语句 放在<select>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是否支撑自定义语言
* <delete id="delEmployeeById" parameterType="int" lang="chengLang">
<settings>
<setting name="defaultScriptingLanguage" value="chengLang"/>
</settings>
*/
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: <selectKey> and <include> 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);
/**
*<insert id="saveUser" parameterType="com.cheng.entity.User" useGeneratedKeys="true" keyProperty="id">
*判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象
*/
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
/**
* 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,
* 否者就看我们的mybatis-config.xml配置文件中是配置了
* <setting name="useGeneratedKeys" value="true"></setting> 默认是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,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
将解析后的构建mappedStatment
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
/**
* 为我们的sqlId 拼接上命名空间
*/
id = applyCurrentNamespace(id, false);
/**
* 判断是否是select的标签类型
*/
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
/**
* 通过MapperStatment的构建器构建对象
*/
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
/**
* 处理我们的paramMap和parameterType
*/
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
/**
* 构建我们的的MappedStatment对象
*/
MappedStatement statement = statementBuilder.build();
/**
* 把mappedStatment对象加入到配置类中
*/
configuration.addMappedStatement(statement);
return statement;
}
最终关键的将解析的sql节点封装成MappedStatement 放入mybatis全局配置configuration的map中
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
后续项目里执行dao可以通过全限定名+sqlId可以到这缓存map里找
回到最初xmlMapperBuilder.parse();
解析的代码,还有一个点
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//上面分析这个解析xml文件的具体过程
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定mapper全限定名(把mapper class保存到knowMappers缓存map中)
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
进入bindMapperForNamespace();
private void bindMapperForNamespace() {
//获取全限定名称
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//获取实际类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
//添加缓存
configuration.addMapper(boundType);
}
}
}
}
进入configuration.addMapper(boundType);
来到Configuration#addMapper()
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
进入mapperRegistry.addMapper(type);
来到MapperRegister#addMapper(type)
public <T> void addMapper(Class<T> type) {
/**
* 判断我们传入进来的type类型是不是接口
*/
if (type.isInterface()) {
/**
* 判断我们的缓存中有没有该类型
*/
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/**
* 创建一个MapperProxyFactory 把我们的Mapper接口保存到工厂类中
*/
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try. mapper注解构造器
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
/**
* 进行解析
*/
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
最终会new MapperProxyFactory<>(type)
放入到knownMappers缓存map
到这xml的解析已经结束啦
重点记住这两个缓存map:
- MappedStatement (后续项目里执行dao可以通过全限定名+sqlId可以到这缓存map里找)
knownMappers(new MapperProxyFactory<>(type))
,后面对dao接口创建动态代理时会用到
这两个map具体的作用期待后面继续解析吧