每天用Mybatis,但是Mybatis的工作原理你真的知道吗?-创新互联
近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。
成都创新互联公司公司2013年成立,先为瑞丽等服务建站,瑞丽等地企业,进行企业商务咨询服务。为瑞丽企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。核心部件:
SqlSession
Executor
StatementHandler
ParameterHandler
ResultSetHandler
TypeHandler
MappedStatement
Configuration
在分析工作原理之前,首先看一下我的mybatis全局配置文件
第一步:创建一个sqlSessionFactory
在了解如何创建sqlSessionFactory之前,先看一下mybatis是如何加载全局配置文件,解析xml文件生成Configuration的
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
private void parseConfiguration(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) 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); 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); 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."); } } } } }
根据以上代码可以分析,在写mapper映射文件的地址时不仅可以写成resource,还可以写成url和mapperClass的形式,由于我们用的是resource,所以直接进入第一个判断,最后解析mapper映射文件的方法是
private void configurationElement(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 void parseStatementNode() { ... 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中。
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) { if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved"); id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = 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); MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }
最后回到我们的创建sqlSessionFactory上,之前的一切都是为了生成一个sqlSessionFactory服务的
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(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. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
从上面的代码可以看出最后是通过以Configuration为参数build()方法生成DefautSqlSessionFactory。
第二步:创建sqlSession
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 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 new DefaultSqlSession(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, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; }
executor在这一步得到创建,具体的使用在下一步。
第三步:执行具体的sql请求
在我的代码里执行的是
User user = 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 的执行器 Executor List result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } 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") 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) throw new ExecutorException("Executor was closed."); if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; 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 } } return list; }
privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; 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); } return list; }
在这两个方法里面会为当前的查询创建一个缓存key,如果缓存中没有值,直接从数据库中读取,执行查询后将得到的list结果放入缓存之中。
紧接着看doQuery()在SimpleExecutor类中重写的方法
publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 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) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection); handler.parameterize(stmt); return stmt; }
publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler. handleResultSets(ps); }
prepareStatement()是创建Statement的具体实现方法,调用parameterize()对创建的Statement对象设置参数,即为我们设为占位符的地方赋上指定的参数,parameterize()方法再深入进去就是调用ParameterHandler的setParameters()方法具体赋值了。欢迎大家关注我的公众号【程序员追风】,文章都会在里面更新,整理的资料也会放在里面
这里的query()是调用了ResultSetHandler的handleResultSets(Statement) 方法。作用就是把ResultSet结果集对象转换成List类型的集合。
总结以上步骤就是:
根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示
为当前的查询创建一个缓存Key
缓存中没有值,直接从数据库中读取数据
执行查询,返回List 结果,然后 将查询的结果放入缓存之中
根据既有的参数,创建StatementHandler对象来执行查询操作
将创建Statement传递给StatementHandler对象,调用parameterize()方法赋值
调用StatementHandler.query()方法,返回List结果集
总结
以上三个步骤所有流程大体可以用一张图来总结
最后
欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。
当前标题:每天用Mybatis,但是Mybatis的工作原理你真的知道吗?-创新互联
分享网址:http://scjbc.cn/article/hishj.html