美文网首页
了解Mybatis的工作原理吗?

了解Mybatis的工作原理吗?

作者: ApesKingMan | 来源:发表于2021-04-17 19:01 被阅读0次

    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

    相关文章

      网友评论

          本文标题:了解Mybatis的工作原理吗?

          本文链接:https://www.haomeiwen.com/subject/tbourktx.html