了解Mybatis的工作原理吗?

0 前言

一个Mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。

核心部件:

SqlSessionExecutorStatementHandlerParameterHandlerResultSetHandlerTypeHandlerMappedStatementConfiguration

在分析工作原理之前,首先看一下我的mybatis全局配置文件

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">                           

1 第一步:创建一个sqlSessionFactory

在了解如何创建sqlSessionFactory之前,先看一下mybatis是如何加载全局配置文件,解析xml文件生成Configuration的

publicConfiguration parse() {if(parsed) {thrownewBuilderException("Each XMLConfigBuilder can>    }    parsed = true;    parseConfiguration(parser.evalNode("/configuration"));    return configuration;  }

privatevoidparseConfiguration(XNode root){try{propertiesElement(root.evalNode("properties"));//issue #117 read properties firsttypeAliasesElement(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 #631databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));}catch(Exception e) {thrownewBuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e);    }  }

在上面的第二段代码中有一句 

mapperElement(root.evalNode("mappers"));

刚好我们的全局配置文件中有一个mapper的配置,由此可见,mapperElemet()方法是解析mapper映射文件的,具体代码如下: 

privatevoid mapperElement(XNodeparent)throwsException{if(parent != null) {for(XNodechild : parent.getChildren()) {if("package".equals(child.getName())) {StringmapperPackage = child.getStringAttribute("name");          configuration.addMappers(mapperPackage);}else{Stringresource = child.getStringAttribute("resource");Stringurl = child.getStringAttribute("url");StringmapperClass = child.getStringAttribute("class");if(resource != null && url == null && mapperClass == null) {//进入该判断ErrorContext.instance().resource(resource);InputStreaminputStream =Resources.getResourceAsStream(resource);XMLMapperBuildermapperParser = newXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());            mapperParser.parse();}elseif(resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStreaminputStream =Resources.getUrlAsStream(url);XMLMapperBuildermapperParser = newXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());            mapperParser.parse();}elseif(resource == null && url == null && mapperClass != null) {Class mapperInterface =Resources.classForName(mapperClass);            configuration.addMapper(mapperInterface);}else{thrownewBuilderException("A mapper element may>          }        }      }    }  }

根据以上代码可以分析,在写mapper映射文件的地址时不仅可以写成resource,还可以写成url和mapperClass的形式,由于我们用的是resource,所以直接进入第一个判断,最后解析mapper映射文件的方法是 

privatevoidconfigurationElement(XNode context){try{Stringnamespace= context.getStringAttribute("namespace");if(namespace.equals("")) {thrownewBuilderException("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) {thrownewBuilderException("Error parsing Mapper XML. Cause: "+ e, e);    }  }

其中具体解析每一个sql语句节点的是 

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

进入这个方法一层层深究,最后到这里可以知道MappedStatement是由builderAssistant(即MapperBuildAssistant)创建的。

publicvoidparseStatementNode(){    ...    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,        resultSetTypeEnum, flushCache, useCache, resultOrdered,        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);  }

最后进入方法addMappedStatement(),mappedStatement最后以id为键保存在了Configuration中的一个map变量mappedStatements中。


publicMappedStatement addMappedStatement(Stringid,SqlSourcesqlSource,StatementTypestatementType,SqlCommandTypesqlCommandType,IntegerfetchSize,Integertimeout,StringparameterMap,Class<?>parameterType,StringresultMap,Class<?>resultType,ResultSetTyperesultSetType,booleanflushCache,booleanuseCache,booleanresultOrdered,KeyGeneratorkeyGenerator,StringkeyProperty,StringkeyColumn,StringdatabaseId,LanguageDriverlang,StringresultSets) {if(unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");id=applyCurrentNamespace(id, false);booleanisSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.BuilderstatementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);statementBuilder.resource(resource);statementBuilder.fetchSize(fetchSize);statementBuilder.statementType(statementType);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);statementBuilder.keyColumn(keyColumn);statementBuilder.databaseId(databaseId);statementBuilder.lang(lang);statementBuilder.resultOrdered(resultOrdered);statementBuilder.resulSets(resultSets);setStatementTimeout(timeout,statementBuilder);setStatementParameterMap(parameterMap,parameterType, statementBuilder);setStatementResultMap(resultMap,resultType, resultSetType, statementBuilder);setStatementCache(isSelect,flushCache, useCache, currentCache, statementBuilder);MappedStatementstatement = statementBuilder.build();configuration.addMappedStatement(statement);returnstatement;  }

最后回到我们的创建sqlSessionFactory上,之前的一切都是为了生成一个sqlSessionFactory服务的 

publicSqlSessionFactorybuild(InputStream inputStream, String environment, Properties properties){try{XMLConfigBuilder parser =newXMLConfigBuilder(inputStream, environment, properties);returnbuild(parser.parse());}catch(Exception e) {throwExceptionFactory.wrapException("Error building SqlSession.", e);}finally{      ErrorContext.instance().reset();try{        inputStream.close();}catch(IOException e) {// Intentionally ignore. Prefer previous error.      }    }  }publicSqlSessionFactorybuild(Configuration config){returnnewDefaultSqlSessionFactory(config);  }

从上面的代码可以看出最后是通过以Configuration为参数build()方法生成DefautSqlSessionFactory。

2 创建sqlSession

publicSqlSessionopenSession(){returnopenSessionFromDataSource(configuration.getDefaultExecutorType(),null,false);  }

privateSqlSessionopenSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,booleanautoCommit){Transaction tx =null;try{finalEnvironment environment = configuration.getEnvironment();finalTransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);finalExecutor executor = configuration.newExecutor(tx, execType);returnnewDefaultSqlSession(configuration, executor, autoCommit);}catch(Exception e) {closeTransaction(tx);// may have fetched a connection so lets call close()throwExceptionFactory.wrapException("Error opening session.  Cause: "+ e, e);}finally{      ErrorContext.instance().reset();    }  }

//返回一个SqlSession,默认使用DefaultSqlSession publicDefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty =false;this.autoCommit = autoCommit;  }

executor在这一步得到创建,具体的使用在下一步。

3 执行具体的sql请求

在我的代码里执行的是  

Useruser = sqlSession.selectOne("test.findUserById",1);

具体到里面的方法就是  

publicList selectList(String statement, Object parameter, RowBounds rowBounds) {try{//1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement      MappedStatement ms = configuration.getMappedStatement(statement);//2. 将查询任务委托给MyBatis 的执行器 ExecutorList result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);returnresult;}catch(Exceptione) {throwExceptionFactory.wrapException("Error querying database.  Cause: "+ e, e);}finally{      ErrorContext.instance().reset();    }  }

在这里通过statementId拿到了我们在第一步存在map里面的MappedStatement。在这里引用参考博客的一句话:

SqlSession根据Statement ID, 在mybatis配置对象Configuration中获取到对应的MappedStatement对象,然后调用mybatis执行器来执行具体的操作。

再继续看query()和queryFromDatabase()这两个方法 

@SuppressWarnings("unchecked")publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if(closed)thrownewExecutorException("Executor was closed.");if(queryStack ==0&& ms.isFlushCacheRequired()) {      clearLocalCache();    }Listlist;try{      queryStack++;list= resultHandler ==null? (List) localCache.getObject(key) :null;if(list!=null) {        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);}else{list= queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);      }}finally{      queryStack--;    }if(queryStack ==0) {for(DeferredLoad deferredLoad : deferredLoads) {        deferredLoad.load();      }deferredLoads.clear();// issue #601if(configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {clearLocalCache();// issue #482      }    }returnlist;  }

privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {Listlist;    localCache.putObject(key, EXECUTION_PLACEHOLDER);try{list= doQuery(ms, parameter, rowBounds, resultHandler, boundSql);}finally{      localCache.removeObject(key);    }localCache.putObject(key,list);if(ms.getStatementType() == StatementType.CALLABLE) {      localOutputParameterCache.putObject(key, parameter);    }returnlist;  }

在这两个方法里面会为当前的查询创建一个缓存key,如果缓存中没有值,直接从数据库中读取,执行查询后将得到的list结果放入缓存之中。

紧接着看doQuery()在SimpleExecutor类中重写的方法 

publicListdoQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throwsSQLException{Statement stmt =null;try{      Configuration configuration = ms.getConfiguration();      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);      stmt = prepareStatement(handler, ms.getStatementLog());returnhandler.query(stmt, resultHandler);}finally{      closeStatement(stmt);    }  }

Statement连接对象就是在这里创建的,因此Executor的作用之一就是创建Statement了,创建完后又把Statement丢给StatementHandler返回List查询结果。

接下来再看一下这里的两个方法prepareStatement()和query()的具体实现  

privateStatement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statementstmt;Connectionconnection = getConnection(statementLog);stmt=handler.prepare(connection);handler.parameterize(stmt);returnstmt;}

publicListquery(Statement statement, ResultHandler resultHandler)throwsSQLException{    PreparedStatement ps = (PreparedStatement) statement;    ps.execute();returnresultSetHandler. handleResultSets(ps);  }

prepareStatement()是创建Statement的具体实现方法,调用parameterize()对创建的Statement对象设置参数,即为我们设为占位符的地方赋上指定的参数,parameterize()方法再深入进去就是调用ParameterHandler的setParameters()方法具体赋值了。

这里的query()是调用了ResultSetHandler的handleResultSets(Statement) 方法。

作用就是把ResultSet结果集对象转换成List类型的集合。 

总结以上步骤就是:

1、根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示2、为当前的查询创建一个缓存Key3、缓存中没有值,直接从数据库中读取数据4、执行查询,返回List结果,然后 将查询的结果放入缓存之中5、根据既有的参数,创建StatementHandler对象来执行查询操作6、将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值7、调用StatementHandler.query()方法,返回List结果集

总结以上三个步骤所有流程大体可以用一张图来总结:

参考:

https://blog.csdn.net/luanlouis/article/details/40422941

https://blog.csdn.net/a412451848/article/details/82723754

https://mp.weixin.qq.com/s/zkk0QAcL7p9aVZNHsdActw

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容