1:我们从MyBatis配置开始解析
解析入口在 SqlSessionFactoryBean.class中,至于为什么,大家可以查看 juejin.cn/post/689673… 这篇文文章,里面解析了SpringBoot+MyBatis的整合源码解析
2:开始解析
public void parse() {
//判断该配置文件(编写Sql的xml文件)是否已经被解析过,解析过的配置文件会被放到Set集合当中
if (!configuration.isResourceLoaded(resource)) {
//开始解析(编写Sql的xml文件)mapper节点,我们进入configurationElement方法
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
复制代码
3:进入configurationElement方法
//这里就开始解析mapper节点下的各种属性信息了,我们重点看下解析select|insert|update|delete等元素信息
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//我们重点解析下面这个方法
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);
}
}
复制代码
4:进入buildStatementFromContext()方法
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
复制代码
5:继续进入下面buildStatementFromContext(list, null);
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//进入这个方法
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
复制代码
6:进入statementParser.parseStatementNode();
public void parseStatementNode() {
/**
* 中间省略很多代码,但是重点是下面这行代码
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
复制代码
7:我们继续跟进builderAssistant.addMappedStatement方法
//这里我截取了一段代码,重点是MapperStatement这个对象,可以说我们执行一条Sql语句的相关信息都在这个对象里面了
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
复制代码
8:查看MapperStatement对象
MapperStatement有几个很重要的属性
id: 在我们执行Mapper接口一个方法的时候,需要依靠这个id找到对应的MapperStatement对象,然后拿到其中的Sql语句
SqlSource:这里面就封装了我们在xml配置文件中编写的SQL语句,大家在图中就可以看到了
9: MapperStatement对象总结
现在可以对MapperStatement做个总结了:MyBatis在解析我们编写的xml配置的时候,当我们解析一条Sql语句的时候,那么就会把对应的id,sql语句都封装到MapperStatement这个对象中
10:MapperStatement中id的组成
id = namespace + id
所以我们在执行Mapper接口的时候,会通过Mapper接口的 全路径名+方法名组成的id去寻找对应的MapperStatement对象,就是通过对比id来进行比较的
11:现在继续第7步
在生成了MapperStatement对象之后,MyBatis会把这个对象存储到一个Map中,而key值就是MapperStatement对象中的id
12:总结
所以如果我们在Mapper接口中有同样名称的方法,那么在生成MapperStatement对象的时候,id就会重复,也就意味着之前存储的MapperStatement对象就会被覆盖掉
map.put("com.coco.mapper.UserDao.query",MapperStatement1);
map.put("com.coco.mapper.UserDao.query",MapperStatement2);
那么MapperStatement1就会被覆盖掉,那么会出现什么问题呢?
比如Mapper接口方法如下
User query(Integer id);
所对应的sql = select * from user where id = #{id}
User query(String name);
所对应的sql = select * from user where name = #{name}
因为先解析的MapperStatement对象被后面的覆盖掉了,比如MyBatis先解析了第一个方法,然后再解析第二个方法,那么第一个方法被覆盖掉了,当我们执行第一个方法的时候,通过id就会找到第二个方法的MapperStatement对象了,那么Sql语句就错了
13:结束语
现在大家能理解为什么Mapper接口方法不能重载的原理了嘛