回复1024有特别礼包
作者:江南入直 | 来源:/scuury/p/10371246.html
上一篇:微信支付的架构到底有多牛?
近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。
核心部件:
SqlSessionExecutorStatementHandlerParameterHandlerResultSetHandlerTypeHandlerMappedStatementConfiguration
在分析工作原理之前,首先看一下我的mybatis全局配置文件
configuration PUBLIC"-////DTDConfig3.0//EN" "/dtd/mybatis-3-config.dtd"> default="development">"development">"JDBC"/>"POOLED">"driver"value="com.mysql.jdbc.Driver"/>"url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>"username"value="root"/>"password"value="123456"/>"sqlMapper/userMapper.xml"/><?xml version="1.0"encoding="UTF-8"?>
第一步:创建一个sqlSessionFactory
在了解如何创建sqlSessionFactory之前,先看一下mybatis是如何加载全局配置文件,解析xml文件生成Configuration的
if(parsed){ thrownewBuilderException("EachXMLConfigBuildercanonlybeusedonce."); } parsed=true; parseConfiguration(parser.evalNode("/configuration")); returnconfiguration; }publicConfigurationparse(){
try{ propertiesElement(root.evalNode("properties"));//issue#117readpropertiesfirst typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments"));//readitafterobjectFactoryandobjectWrapperFactoryissue#631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); }catch(Exceptione){ thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e); } }privatevoidparseConfiguration(XNoderoot){
在上面的第二段代码中有一句
mapperElement(root.evalNode("mappers"));
刚好我们的全局配置文件中有一个mapper的配置,由此可见,mapperElemet()方法是解析mapper映射文件的,具体代码如下
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("Amapperelementmayonlyspecifyaurl,resourceorclass,butnotmorethanone."); } } } } }privatevoidmapperElement(XNodeparent)throwsException{
根据以上代码可以分析,在写mapper映射文件的地址时不仅可以写成resource,还可以写成url和mapperClass的形式,由于我们用的是resource,所以直接进入第一个判断,最后解析mapper映射文件的方法是
try{ Stringnamespace=context.getStringAttribute("namespace"); if(namespace.equals("")){ thrownewBuilderException("Mapper'snamespacecannotbeempty"); } 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(Exceptione){ thrownewBuilderException("ErrorparsingMapperXML.Cause:"+e,e); } }privatevoidconfigurationElement(XNodecontext){
其中具体解析每一个sql语句节点的是
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
进入这个方法一层层深究,最后到这里可以知道MappedStatement是由builderAssistant(即MapperBuildAssistant)创建的。
... builderAssistant.addMappedStatement(id,sqlSource,statementType,sqlCommandType, fetchSize,timeout,parameterMap,parameterTypeClass,resultMap,resultTypeClass, resultSetTypeEnum,flushCache,useCache,resultOrdered, keyGenerator,keyProperty,keyColumn,databaseId,langDriver,resultSets); }publicvoidparseStatementNode(){
最后进入方法addMappedStatement(),mappedStatement最后以id为键保存在了Configuration中的一个map变量mappedStatements中。
Stringid, SqlSourcesqlSource, StatementTypestatementType, SqlCommandTypesqlCommandType, IntegerfetchSize, Integertimeout, StringparameterMap, Class>parameterType, StringresultMap, Class>resultType, ResultSetTyperesultSetType,booleanflushCache,booleanuseCache,booleanresultOrdered, KeyGeneratorkeyGenerator, StringkeyProperty, StringkeyColumn, StringdatabaseId, LanguageDriverlang, StringresultSets){ if(unresolvedCacheRef)thrownewIncompleteElementException("Cache-refnotyetresolved"); id=applyCurrentNamespace(id,false); booleanisSelect=sqlCommandType==SqlCommandType.SELECT; MappedStatement.BuilderstatementBuilder=newMappedStatement.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; }publicMappedStatementaddMappedStatement(
最后回到我们的创建sqlSessionFactory上,之前的一切都是为了生成一个sqlSessionFactory服务的
搜索公众号顶级架构师回复关键字“offer”,获取一份算法面试题和答案惊喜礼包。
try{ XMLConfigBuilderparser=newXMLConfigBuilder(inputStream,environment,properties); returnbuild(parser.parse()); }catch(Exceptione){ throwExceptionFactory.wrapException("ErrorbuildingSqlSession.",e); }finally{ ErrorContext.instance().reset(); try{ inputStream.close(); }catch(IOExceptione){ //Intentionallyignore.Preferpreviouserror. } } } publicSqlSessionFactorybuild(Configurationconfig){ returnnewDefaultSqlSessionFactory(config); }publicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){
从上面的代码可以看出最后是通过以Configuration为参数build()方法生成DefautSqlSessionFactory。
第二步:创建sqlSession
returnopenSessionFromDataSource(configuration.getDefaultExecutorType(),null,false); }publicSqlSessionopenSession(){
Transactiontx=null; try{ finalEnvironmentenvironment=configuration.getEnvironment(); finalTransactionFactorytransactionFactory=getTransactionFactoryFromEnvironment(environment); tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit); finalExecutorexecutor=configuration.newExecutor(tx,execType); returnnewDefaultSqlSession(configuration,executor,autoCommit); }catch(Exceptione){ closeTransaction(tx);//mayhavefetchedaconnectionsoletscallclose() throwExceptionFactory.wrapException("Erroropeningsession.Cause:"+e,e); }finally{ ErrorContext.instance().reset(); } }privateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit){
publicDefaultSqlSession(Configurationconfiguration,Executorexecutor,booleanautoCommit){ this.configuration=configuration; this.executor=executor; this.dirty=false; this.autoCommit=autoCommit; }//返回一个SqlSession,默认使用DefaultSqlSession
executor在这一步得到创建,具体的使用在下一步。
第三步:执行具体的sql请求
在我的代码里执行的是
Useruser=sqlSession.selectOne("test.findUserById",1);
具体到里面的方法就是
MappedStatementms=configuration.getMappedStatement(statement);//2.将查询任务委托给MyBatis的执行器Executor Listresult=executor.query(ms,wrapCollection(parameter),rowBounds,Executor.NO_RESULT_HANDLER);returnresult; }catch(Exceptione){throwExceptionFactory.wrapException("Errorqueryingdatabase.Cause:"+e,e); }finally{ ErrorContext.instance().reset(); } }publicListselectList(Stringstatement,Objectparameter,RowBoundsrowBounds){try{//1.根据StatementId,在mybatis配置对象Configuration中查找和配置文件相对应的MappedStatement
在这里通过statementId拿到了我们在第一步存在map里面的MappedStatement。在这里引用参考博客的一句话:
SqlSession根据Statement ID, 在mybatis配置对象Configuration中获取到对应的MappedStatement对象,然后调用mybatis执行器来执行具体的操作。
再继续看query()和queryFromDatabase()这两个方法
publicListquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{ ErrorContext.instance().resource(ms.getResource()).activity("executingaquery").object(ms.getId());if(closed)thrownewExecutorException("Executorwasclosed.");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(DeferredLoaddeferredLoad:deferredLoads){ deferredLoad.load(); } deferredLoads.clear();//issue#601if(configuration.getLocalCacheScope()==LocalCacheScope.STATEMENT){ clearLocalCache();//issue#482 } }returnlist; }@SuppressWarnings("unchecked")
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; }privateListqueryFromDatabase(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeykey,BoundSqlboundSql)throwsSQLException{
在这两个方法里面会为当前的查询创建一个缓存key,如果缓存中没有值,直接从数据库中读取,执行查询后将得到的list结果放入缓存之中。
紧接着看doQuery()在SimpleExecutor类中重写的方法
Statementstmt=null;try{ Configurationconfiguration=ms.getConfiguration(); StatementHandlerhandler=configuration.newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql); stmt=prepareStatement(handler,ms.getStatementLog());returnhandler.query(stmt,resultHandler); }finally{ closeStatement(stmt); } }publicListdoQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{
Statement连接对象就是在这里创建的,因此Executor的作用之一就是创建Statement了,创建完后又把Statement丢给StatementHandler返回List查询结果。
接下来再看一下这里的两个方法prepareStatement()和query()的具体实现
Statementstmt; Connectionconnection=getConnection(statementLog); stmt=handler.prepare(connection); handler.parameterize(stmt); returnstmt; }privateStatementprepareStatement(StatementHandlerhandler,LogstatementLog)throwsSQLException{
PreparedStatementps=(PreparedStatement)statement; ps.execute();returnresultSetHandler.handleResultSets(ps); }publicListquery(Statementstatement,ResultHandlerresultHandler)throwsSQLException{
prepareStatement()是创建Statement的具体实现方法,调用parameterize()对创建的Statement对象设置参数,即为我们设为占位符的地方赋上指定的参数,parameterize()方法再深入进去就是调用ParameterHandler的setParameters()方法具体赋值了。
搜索公众号顶级架构师回复关键字“架构整洁”,获取一份惊喜礼包。
这里的query()是调用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet结果集对象转换成List类型的集合。
总结以上步骤就是:
根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示为当前的查询创建一个缓存Key缓存中没有值,直接从数据库中读取数据执行查询,返回List 结果,然后 将查询的结果放入缓存之中根据既有的参数,创建StatementHandler对象来执行查询操作将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值调用StatementHandler.query()方法,返回List结果集
总结
以上三个步骤所有流程大体可以用一张图来总结
PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。
大家一起在评论区聊聊呗~
公众号后台回复架构或者架构整洁有惊喜礼包!顶级架构师交流群
「顶级架构师」建立了读者架构师交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的架构师们一起交流学习。
扫描添加好友邀你进架构师群,加我时注明【姓名+公司+职位】
版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。
猜你还想看
Java基于Swing和Netty仿QQ界面聊天小项目
图解 Docker 架构
Java压缩20M文件从30秒到1秒的优化过程
MyBatis 如何兼容所有日志框架?