最后
俗话说,好学者临池学书,不过网络时代,对于大多数的我们来说,我倒是觉得学习意识的觉醒很重要,这是开始学习的转折点,比如看到对自己方向发展有用的信息,先收藏一波是一波,比如如果你觉得我这篇文章ok,先点赞收藏一波。这样,等真的沉下心来学习,不至于被找资料分散了心神。慢慢来,先从点赞收藏做起,加油吧!
另外,给大家安排了一波学习面试资料:
以上就是本文的全部内容,希望对大家的面试有所帮助,祝大家早日升职加薪迎娶白富美走上人生巅峰!
我们注意到这里有两三个与Mapper相关的配置:
-
SqlSessionFactory#mapperLocations,指定xml文件的配置路径。
-
SqlSessionFactory#configLocation,指定mybaits的配置文件,该配置文件也可以配置mapper.xml的配置路径信息。
-
MapperScannerConfigurer,扫描Mapper的java类(DAO)。
我们已经详细介绍了Mybatis Mapper对象的扫描与构建,那接下来我们将重点介绍MaperProxy与mapper.xml文件是如何建立关联关系的。
根据上面的罗列以及上文的讲述,Mapper.xml与Mapper建立联系主要的入口有三:
1)MapperScannerConfigurer扫描Bean流程中,在调用MapperReigistry#addMapper时如果Mapper对应的映射文件(Mapper.xml)未加载到内存,会触发加载。
2)实例化SqlSessionFactory时,如果配置了mapperLocations。
3)示例化SqlSessionFactory时,如果配置了configLocation。
本节的行文思路:从SqlSessionFacotry的初始化开始讲起,因为mapperLocations、configLocation都是是SqlSessionFactory的属性。
温馨提示:下面开始从源码的角度对其进行介绍,大家可以先跳到文末看看其调用序列图。
if (xmlConfigBuilder != null) { // XMLConfigBuilder // @1
try {
xmlConfigBuilder.parse();
if (logger.isDebugEnabled()) {
logger.debug(“Parsed configuration file: '” + this.configLocation + “'”);
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (!isEmpty(this.mapperLocations)) { // @2
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
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 (logger.isDebugEnabled()) {
logger.debug(“Parsed mapper file: '” + mapperLocation + “'”);
}
}
} else {
if (logger.isDebugEnabled()) {
logger.debug(“Property ‘mapperLocations’ was not specified or no matching resources found”);
}
}
上文有两个入口:
代码@1:处理configLocation属性。
代码@2:处理mapperLocations属性。
我们先从XMLConfigBuilder#parse开始进行追踪。该方法主要是解析configLocation指定的配置路径,对其进行解析,具体调用parseConfiguration方法。
2.1 XMLConfigBuilder
我们直接查看其parseConfiguration方法。
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode(“properties”)); //issue #117 read properties first
typeAliasesElement(root.evalNode(“typeAliases”));
pluginElement(root.evalNode(“plugins”));
objectFactoryElement(root.evalNode(“objectFactory”));
objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”));
settingsElement(root.evalNode(“settings”));
environmentsElement(root.evalNode(“environments”)); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode(“databaseIdProvider”));
typeHandlerElement(root.evalNode(“typeHandlers”));
mapperElement(root.evalNode(“mappers”)); // @1
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
重点关注mapperElement,从名称与参数即可以看出,该方法主要是处理中mappers的定义,即mapper sql语句的解析与处理。如果使用过Mapper的人应该不难知道,我们使用mapper节点,通过resource标签定义具体xml文件的位置。
2.1.1XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if (“package”.equals(child.getName())) {
String mapperPackage = child.getStringAttribute(“name”);
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute(“resource”);
String url = child.getStringAttribute(“url”);
String mapperClass = child.getStringAttribute(“class”);
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()); // @1
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<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException(“A mapper element may only specify a url, resource or class, but not more than one.”);
}
}
}
}
}
上面的代码比较简单,不难看出,解析出Mapper标签,解析出resource标签的属性,创建对应的文件流,通过构建XMLMapperBuilder来解析对应的mapper.xml文件。此时大家会惊讶的发现,在SqlSessionFacotry的初始化代码中,处理mapperLocations时就是通过构建XMLMapperBuilder来解析mapper文件,其实也不难理解,因为这是mybatis支持的两个地方可以使用mapper标签来定义mapper映射文件,具体解析代码当然是一样的逻辑。那我们解析来重点把目光投向XMLMapperBuilder。
2.2 XMLMapperBuilder
XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) { // @1
configurationElement(parser.evalNode(“/mapper”));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps(); // @2
parsePendingChacheRefs(); // @3
parsePendingStatements(); // @4
}
代码@1:如果该映射文件(*.Mapper.xml)文件未加载,则首先先加载,完成xml文件的解析,提取xml中与mybatis相关的数据,例如sql、resultMap等等。
代码@2:处理mybatis xml中ResultMap。
代码@3:处理mybatis缓存相关的配置。
代码@4:处理mybatis statment相关配置,这里就是本篇关注的,Sql语句如何与Mapper进行关联的核心实现。
接下来我们重点探讨parsePendingStatements()方法,解析statement(对应SQL语句)。
2.2.1 XMLMapperBuilder#parsePendingStatements
private void parsePendingStatements() {
Collection incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator iter = incompleteStatements.iterator(); // @1
while (iter.hasNext()) {
try {
iter.next().parseStatementNode(); // @2
iter.remove();
} catch (IncompleteElementException e) {
// Statement is still missing a resource…
}
}
}
}
代码@1:遍历解析出来的所有SQL语句,用的是XMLStatementBuilder对象封装的,故接下来重点看一下代码@2,如果解析statmentNode。
2.2.2 XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
String id = context.getStringAttribute(“id”); // @1 start
String databaseId = context.getStringAttribute(“databaseId”);
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
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);
最后
看完美团、字节、腾讯这三家的面试问题,是不是感觉问的特别多,可能咱们又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。
开篇有提及我可是足足背下了1000道题目,多少还是有点用的呢,我看了下,上面这些问题大部分都能从我背的题里找到的,所以今天给大家分享一下互联网工程师必备的面试1000题。
注意不论是我说的互联网面试1000题,还是后面提及的算法与数据结构、设计模式以及更多的Java学习笔记等,皆可分享给各位朋友
互联网工程师必备的面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
[外链图片转存中…(img-sObJbNto-1715438526302)]
互联网工程师必备的面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
[外链图片转存中…(img-tChsST8M-1715438526302)]