MyBatis源码分析
创始人
2024-06-02 17:32:54

Mybatis作为开发中最常用的数据持久层框架,深受广大程序员喜爱。下面我将通过debug分析mybatis加载资源和执行sql的流程:

一、配置mybatis-config.xml:以下配置为求简略,直接从官网搬运MyBatis官网




加载mybatis配置文件,并创建sqlsessionFactory:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

debug断点进入build方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {//获取一个xml解析器XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//解析xmlreturn build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (inputStream != null) {inputStream.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}
}

进入parse.parse():可以看到是由Configuration 的parse方法加载并解析Mybatis-config.xml配置文件:

public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;//解析node、configurationparseConfiguration(parser.evalNode("/configuration"));return configuration;
}

通过断点获取结果:parser.evalNode("/configuration")

xnode:由此可见和我们再配置文件中的配置是一致的:

                                                                                        

配置文件截图: 

进一步进入:parseConfiguration:此段代码揭示了,mybatis解析配置文件的顺序:

private void parseConfiguration(XNode root) {try {// issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}

扩展:parseConfiguration 解释了mybatis解析配置文件的顺序,由此可见,我们再配置文件中的属性配置不能随便变更顺序。

 第一段:解析properties文件:

propertiesElement(root.evalNode("properties"));

 将解析结果保存到:XPathParser

parser.setVariables(defaults); configuration.setVariables(defaults);

public class XPathParser {private final Document document;private boolean validation;private EntityResolver entityResolver;private Properties variables;private XPath xpath;
}
 

 

解析config中占位符,替换:

 

解析mapper:

mapperElement(root.evalNode("mappers"))

 

 解析mapper,由源码可见,mapper从,package->resource->url->class这个顺序解析

  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);try (InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try (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.");}}}}}

 再次进入到

XMLMapperBuilder.parse()
public void parse() {//判断是否已加载过mapperif (!configuration.isResourceLoaded(resource)) {//解析mapperconfigurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();
}

parser.evalNode("/mapper"):解析mapper

进入配置element

private void configurationElement(XNode context) {try {//获取namespaceString namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}//获取各种mapper策略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"));//解析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);}
}

 

 

 加载完毕,加当前mapper加入已加载的rsource中,将将返回值类型方法mapper中,均操作的是:configuration

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 && !configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}
}

加入configuration.addMappedStatement(statement)

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, boolean dirtySelect) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);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(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;
}

扩展:

不可随意更改configuration配置属性位置: 

 扩展二:mybatis mapper的几种方式:resource  url   class  pacakge

 

相关内容

热门资讯

中京电子跌4.95%,成交额6... 1月13日,中京电子跌4.95%,成交额6.01亿元,换手率8.37%,总市值74.07亿元。异动分...
奥克股份跌3.64%,成交额2... 1月13日,奥克股份跌3.64%,成交额2.66亿元,换手率3.75%,总市值70.19亿元。异动分...
凯文教育:大股东有全盘安排,公... 投资者提问:请问海国投2024年提出的注入资产五百亿,提升市值五百亿是真的吗董秘回答(凯文教育SZ0...
顺鑫农业跌0.67%,成交额1... 1月13日,顺鑫农业跌0.67%,成交额1.44亿元,换手率1.30%,总市值110.60亿元。异动...
雅克科技跌1.06%,成交额1... 1月13日,雅克科技跌1.06%,成交额19.26亿元,换手率6.74%,总市值420.96亿元。异...