Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

Mybatis工作原理

江南入直 2019-02-25 14:20:00 阅读数:119 评论数:0 点赞数:0 收藏数:0

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

核心部件:

SqlSession、Executor、StatementHandler、ParameterHandler、ResultSetHandler、TypeHandler、MappedStatement、Configuration

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

第一步:创建一个sqlSessionFactory

在了解如何创建sqlSessionFactory之前,先看一下mybatis是如何加载全局配置文件,解析xml文件生成Configuration的publicConfiguration parse() {if(parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed= true; parseConfiguration(parser.evalNode("/configuration"));returnconfiguration; }

private voidparseConfiguration(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")); }catch(Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " +e, e); } }

 在上面的第二段代码中有一句mapperElement(root.evalNode("mappers"));

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

private void mapperElement(XNode parent) throwsException {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= newXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); }else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream=Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser= newXMLMapperBuilder(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,还可以写成url和mapperClass的形式,由于我们用的是resource,所以直接进入第一个判断,最后解析mapper映射文件的方法是

private voidconfigurationElement(XNode context) {try{ String namespace= context.getStringAttribute("namespace");if (namespace.equals("")) {throw new BuilderException("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) {throw new BuilderException("Error parsing Mapper XML. Cause: " +e, e); } }

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

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

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

public voidparseStatementNode() { ... 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( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, ClassparameterType, String resultMap, ClassresultType, ResultSetType resultSetType,booleanflushCache,booleanuseCache,booleanresultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved"); id= applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType ==SqlCommandType.SELECT; MappedStatement.Builder statementBuilder= 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); MappedStatement statement=statementBuilder.build(); configuration.addMappedStatement(statement);returnstatement; }

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

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

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

第二步:创建sqlSession

publicSqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, booleanautoCommit) { Transaction tx= null;try{final Environment environment =configuration.getEnvironment();final TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment); tx=transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor =configuration.newExecutor(tx, execType);return newDefaultSqlSession(configuration, executor, autoCommit); }catch(Exception e) { closeTransaction(tx);//may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " +e, e); }finally{ ErrorContext.instance().reset(); } }//返回一个SqlSession,默认使用DefaultSqlSession public DefaultSqlSession(Configuration configuration, Executor executor, booleanautoCommit) {this.configuration =configuration;this.executor =executor;this.dirty = false;this.autoCommit =autoCommit; }

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

第三步:执行具体的sql请求

在我的代码里执行的是User user = sqlSession.selectOne("test.findUserById", 1);

具体到里面的方法就是

public ListselectList(String statement, Object parameter, RowBounds rowBounds) {try{//1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement MappedStatement ms =configuration.getMappedStatement(statement);//2. 将查询任务委托给MyBatis 的执行器 Executor List result =executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NORESULTHANDLER);returnresult; }catch(Exception e) {throw ExceptionFactory.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")public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throwsSQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) throw new ExecutorException("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 /#601 if (configuration.getLocalCacheScope() ==LocalCacheScope.STATEMENT) { clearLocalCache();//issue /#482 } }returnlist; }

private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throwsSQLException { 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类中重写的方法public List doQuery(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());return handler.query(stmt, resultHandler); }finally{ closeStatement(stmt); } }

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

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

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throwsSQLException { Statement stmt; Connection connection=getConnection(statementLog); stmt=handler.prepare(connection); handler.parameterize(stmt);returnstmt; }

 

public List query(Statement statement, ResultHandler resultHandler) throwsSQLException { PreparedStatement ps=(PreparedStatement) statement; ps.execute();return resultSetHandler.handleResultSets(ps); }

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

这里的query()是调用了ResultSetHandlerhandleResultSets(Statement) 方法。作用就是把ResultSet结果集对象转换成List类型的集合。

总结以上步骤就是:

1.根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示

2.为当前的查询创建一个缓存Key

3.缓存中没有值,直接从数据库中读取数据

4.执行查询,返回List 结果,然后 将查询的结果放入缓存之中

5.根据既有的参数,创建StatementHandler对象来执行查询操作

6.将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值

7.调用StatementHandler.query()方法,返回List结果集

总结

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

 

这是本人第一次分析框架源码,感受颇深,另外肯定也有不足之处,望批评指正。

最后本博客参考了以下博客:

《深入理解mybatis原理》 MyBatis的架构设计以及实例分析.https://blog.csdn.net/luanlouis/article/details/40422941

debug源码之mybatis.https://blog.csdn.net/a412451848/article/details/82723754

 

版权声明
本文为[江南入直]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/scuury/p/10371246.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领