最近這一週,主要在學習mybatis相關的源碼,因此記錄一下吧,算是一點學習心得spring
我的以爲,mybatis的源碼,大體能夠分爲兩部分,一是原生的mybatis,二是和spring整合以後的mybatis源碼學習(也就是mybatis-spring這個jar包的相關源碼),這邊筆記,主要來學習原生mybatis;sql
仍是先用描述一下,原生mybatis從解析xml到執行SQL的一個流程:數據庫
1.第一步:首先會經過sqlSessionFactoryBuilder來build一個SQLSessionFactory,在build的過程當中,會對mybatis的配置文件、mapper.xml文件進行解析,apache
1.1將mapper.xml中對應的select/update/delete/insert節點,包裝成mappedStatement對象,而後把這個類對象,存方到了一個map中(mappedStatements),key值是namespace的value+select節點的id,value就是mappedStatement;緩存
1.2而後經過反射,根據當前namespace,獲取到一個class,將class存到了另一個map中,knownMappers中,key值是經過反射生成的class對象,value是根據class,生成的MapperProxyFactory對象,這兩個map,在執行查詢的時候,有用到mybatis
2.根據sqlSessionFactoryBuilder生成SqlSessionFactory,再根據sqlSesionFactory,建立sqlSession,這時候,就能夠調用slqSession的selectOne();selectList()來執行查詢,或者經過sqlSession.getMapper()來獲取到接口的代理對象,在執行增刪改查sqlapp
下面,咱們根據上述的步驟來解析mybatis源碼,主要來講如何解析mybatis配置文件ide
1 String resource = "mybatis-config.xml"; 2 InputStream inputStream = Resources.getResourceAsStream(resource); 3 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 4 SqlSession sqlSession = sqlSessionFactory.openSession(); 5 // List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll"); 6 // OperChannel operChannel = sqlSession.selectOne("com.springsource.study.mybatis.OperChannelMapper.selectAll",1); 7 // System.out.println(operChannel.toString()); 8 9 System.out.println("+++++++++++++++++"); 10 OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class); 11 System.out.println(operChannelMapper.selectAll(1));
這是我本身寫的一個測試類,首先咱們從sqlSessionFactoryBuilder().buidl(inputStream)提及:學習
1.首先會獲取到一個XMLConfigBuilder(),而後調用XMLConfigBuilder的parse()方法來解析mybatis的配置文件;須要知道的是,對mybatis配置文件的解析,最後會存放到一個configuration.class中,能夠簡單理解爲和配置文件對應的一個類,在後面,生成的environment、mappedStatements都是configuration的一個屬性測試
1 public Configuration parse() { 2 //在new XMLConfigBuilder的時候,默認置爲了false,表示當前xml只能被解析一次 3 if (parsed) { 4 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 5 } 6 parsed = true; 7 //將配置文件解析成了configuration對象,在該方法中完成 8 parseConfiguration(parser.evalNode("/configuration")); 9 return configuration; 10 } 11 12 /** 13 * @param root 14 * 原生mybatis在執行的時候,解析mybatis配置文件 15 * 這裏解析的節點,都是配置文件中配置的xml信息 16 */ 17 private void parseConfiguration(XNode root) { 18 try { 19 //issue #117 read properties first 20 propertiesElement(root.evalNode("properties")); 21 Properties settings = settingsAsProperties(root.evalNode("settings")); 22 loadCustomVfs(settings); 23 loadCustomLogImpl(settings); 24 typeAliasesElement(root.evalNode("typeAliases")); 25 pluginElement(root.evalNode("plugins")); 26 objectFactoryElement(root.evalNode("objectFactory")); 27 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 28 reflectorFactoryElement(root.evalNode("reflectorFactory")); 29 settingsElement(settings); 30 // read it after objectFactory and objectWrapperFactory issue #631 31 //在這裏解析environment信息,獲取到數據源 32 environmentsElement(root.evalNode("environments")); 33 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 34 typeHandlerElement(root.evalNode("typeHandlers")); 35 //解析mapper配置信息,將SQL封裝成mapperstatement 36 mapperElement(root.evalNode("mappers")); 37 } catch (Exception e) { 38 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 39 } 40 }
在parseConfiguration裏面,主要是對配置文件中各個節點屬性來進行解析;咱們着重來講mapper節點的解析,其中對environment的解析也提一下吧,在獲取數據源的時候,會先獲取到<DataSource>節點中type,根據type建立一個DataSourceFactory,而後把DataSource節點中配置的property屬性的value和name,存到properties中,而後從dataSourceFactory中獲取到一個數據源;
咱們來講對mappers的解析:
在mybatis配置文件中,對mapper.xml的配置方式有四種,分別是package、resource、URL、mapperClass,這四種優先級就是寫的先後順序,由於在源碼中,是按照這四個順序來進行解析的
解析的mappers節點以後,會對節點中的每一個節點進行遍歷,咱們本次,只以resource爲例:
1 private void mapperElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 //在配置文件中,配置mapper.xml文件有四種方式,這裏按照優先級記進行解析 5 if ("package".equals(child.getName())) { 6 String mapperPackage = child.getStringAttribute("name"); 7 configuration.addMappers(mapperPackage); 8 } else { 9 String resource = child.getStringAttribute("resource"); 10 String url = child.getStringAttribute("url"); 11 String mapperClass = child.getStringAttribute("class"); 12 if (resource != null && url == null && mapperClass == null) { 13 ErrorContext.instance().resource(resource); 14 InputStream inputStream = Resources.getResourceAsStream(resource); 15 //實例化一個mapper解析器 16 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 17 //解析SQL語句 18 mapperParser.parse(); 19 } else if (resource == null && url != null && mapperClass == null) { 20 ErrorContext.instance().resource(url); 21 InputStream inputStream = Resources.getUrlAsStream(url); 22 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 23 mapperParser.parse(); 24 } else if (resource == null && url == null && mapperClass != null) { 25 Class<?> mapperInterface = Resources.classForName(mapperClass); 26 configuration.addMapper(mapperInterface); 27 } else { 28 //配置文件的配置只能是這四種,不然會報錯 29 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 30 } 31 } 32 } 33 } 34 }
在mapperParse.parse()方法中,會對<mapper>節點進行解析,而後獲取到mapper節點對應xml文件中的全部屬性
1 private void configurationElement(XNode context) { 2 try { 3 String namespace = context.getStringAttribute("namespace"); 4 if (namespace == null || namespace.equals("")) { 5 throw new BuilderException("Mapper's namespace cannot be empty"); 6 } 7 builderAssistant.setCurrentNamespace(namespace); 8 cacheRefElement(context.evalNode("cache-ref")); 9 cacheElement(context.evalNode("cache")); 10 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 11 resultMapElements(context.evalNodes("/mapper/resultMap")); 12 sqlElement(context.evalNodes("/mapper/sql")); 13 //這裏是解析增刪改查語句的 14 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 15 } catch (Exception e) { 16 throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); 17 } 18 }
這個方法就是對mapper.xml文件中各個節點的解析,其中,比較重要的是對增刪改查節點的解析:
會獲取到當前mapper中全部的增刪改查節點,而後再遍歷,遍歷的時候,會獲取到每一個節點的全部參數信息,因爲這個解析具體參數的篇幅比較長,就不粘貼了,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 在這個方法中;
其中須要提的是,以select爲例,解析到select節點的全部參數和參數值以後,會把因此參數build成一個mappedStatement對象,而後存放到mappedStatements這個map中,key值就是namespace+id(這裏的ID就是select/update...配置的id屬性,通常配置成方法名);
把全部的增刪改查存到mappedStatements以後,會進行另一個操做,就是根據namespace,經過反射,建立一個Class,對象,而後把class對象存到另一個map中,knownMappers
1 private void bindMapperForNamespace() { 2 String namespace = builderAssistant.getCurrentNamespace(); 3 if (namespace != null) { 4 Class<?> boundType = null; 5 try { 6 boundType = Resources.classForName(namespace); 7 } catch (ClassNotFoundException e) { 8 //ignore, bound type is not required 9 } 10 if (boundType != null) { 11 if (!configuration.hasMapper(boundType)) { 12 // Spring may not know the real resource name so we set a flag 13 // to prevent loading again this resource from the mapper interface 14 // look at MapperAnnotationBuilder#loadXmlResource 15 configuration.addLoadedResource("namespace:" + namespace); 16 configuration.addMapper(boundType); 17 } 18 } 19 } 20 }
put方法就在最後一行代碼,configuration.addMapper(boundType);
這裏也比較簡單,就是new MapperProxyFactory()做爲value,class做爲key,而後存到map中,後面會用到
截止到這裏,對配置文件,SQL的解析就完成了,下面咱們來講如何執行SQL
1.List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll");
咱們先說這種方法來請求SQL,這裏就是調用DefaultSqlSession 的selectList()方法,
首先會根據如此那終端額全類名+方法名,從mappedStatements這個map中獲取到mappedStatement對象,這也就是爲何namespace必定要和接口的包名+類名一直的緣由
獲取到mappedStatement對象以後,就是先查詢緩存,緩存中沒有就從數據庫查詢,數據庫查到以後,存到一級緩存中,這裏的大體的原理是這樣的,後面,會單獨寫一遍筆記。來記錄,如何具體執行SQL的
2.OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class);
System.out.println(operChannelMapper.selectAll(1));
這是第二種方式,和第一種方式稍微有些不一樣
這裏的入參是mapperInterface.class,會根據入參,從上面提到的knownMappers中獲取到mapperProxyFactory對象,而後再調用mapperProxyFactory.newInstance()方法來生成代理對象
1 protected T newInstance(MapperProxy<T> mapperProxy) { 2 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); 3 } 4 5 public T newInstance(SqlSession sqlSession) { 6 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); 7 return this.newInstance(mapperProxy); 8 }
在使用jdk代理生成代理對象時,須要傳三個入參,這裏重要是mapperproxy,就是實現了invocationHandler接口的實現類,生成了接口的代理對象以後,
在執行selectAll()的時候,因爲這時候,是代理對象,因此會執行mapperproxy的invoke方法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } if (this.isDefaultMethod(method)) { return this.invokeDefaultMethod(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args);}在這裏,mapperMethod.execute()方法中,會判斷當前SQL是select?update?delete?等,而後調用DefaultSqlSession對應的方法;也就是說,selSession.getMapper()這種方式實際上是經過代理對象來執行SQL的;截止到這裏,基本上就是原生mybatis解析、執行的邏輯,後面有新的認識,會繼續更新