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
网友评论