摘要:最終解析出的和依然是設置到中。到這里,初始化部分就結束了。總結的初始化流程主要是解析配置文件,將相關信息保存在中,同時對每個代表的生成代理對象工廠。
簡介
MyBatis 是 Java 開發中非常流行的 ORM 框架,其封裝了 JDBC 并且解決了 Java 對象與輸入參數和結果集的映射,同時又能夠讓用戶方便地手寫 SQL 語句。MyBatis 的行為類似于以下幾行代碼:
Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, usr, password); PraparedStatement st = conn.prepareStatement(sql); st.setInt(0, 1); st.execute(); ResultSet rs = st.getResultSet(); while (rs.next()) { String result = rs.getString(colname); }
上面是 JDBC 的使用流程,MyBatis 其實就是對上面的代碼進行分解包裝。本文將對 MyBatis 的代碼進行分析,探究其中的邏輯。
基本用法首先從 MyBatis 的基本用法開始,下面是 MyBatis 官網的入門示例:
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
其中 mabatis-config.xml 是 MyBatis 的核心配置文件,其中包括數據源、事務管理器、別名以及 SQL 對應的 Mapper 文件等,如下所示:
有了 SqlSessionFactory 后就可以創建 SqlSession 來調用 select 以及 update 等方法請求數據了:
try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101); } finally { session.close(); }配置文件解析
我們按照上面的代碼流程開始分析源碼,首先是配置文件的解析:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) ,SqlSessionFactoryBuilder 顯然是為了構建 SqlSessionFactory,而且是從配置文件的輸入流構建,代碼如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 創建 XMLConfigBuilder XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // parse.parse() 進行解析 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. } } }
首先是創建了一個 XMLConfigBuilder 對象,它是用來解析 Config 文件的。XMLConfigBuilder 繼承自 BaseBuilder,BaseBuilder 中有個 Configuration 類型的變量,這個類需要重點關注,Config 文件中解析出來的所有信息都保存在這個變量中。
創建了 XMLConfigBuilder 后調用了其 parse 方法:
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 在這個函數中解析 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
這里主要邏輯在 parseConfiguration 中:
private void parseConfiguration(XNode root) { try { // 解析 properties propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 解析 type alias typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析 setting settingsElement(root.evalNode("settings")); // 解析 environment environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 解析 mapper mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
這里解析了 config 文件中所有的標簽,包括 properties、settings 以及 mappers 等,下面挑幾個看一下。
settingssettings 是對 MyBatis 的一些配置項,包括緩存的開啟以及是否使用駝峰轉換(mapUnderscoreToCamelCase)等,代碼如下:
private void settingsElement(XNode context) throws Exception { if (context != null) { // 將配置項保存到 Properties 中 Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); // 默認開啟緩存 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); } }
可以看出,settings 的子節點保存在 Properties 中,然后校驗是否有不合法的子節點,最后提取出其中的屬性保存到 Configuration 中,上面提到這個類專門用于保存 Config 文件解析出的信息。
從上面也可以看到 MyBatis 的一些默認屬性,例如一級緩存如果沒有配置,那么默認是開啟的。
environmentsenvironments 包含了數據源(dataSource) 和事務管理器(transactionManager) 的配置,代碼如下:
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) { // 解析 transactionManager TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 解析 dataSource DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // 設置 environment 到 configuration configuration.setEnvironment(environmentBuilder.build()); } } } } private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); // 通過反射實例化 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); } private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); // 通過反射實例化 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
其中主要是兩部分,第一部分解析 transactionManager,第二部分解析 dataSource。從 transactionManagerElement 和 dataSourceElement 中可以看出通過對應 Class 文件的 newInstance 實例化出對應的工廠對象。最終解析出的 transactionManager 和 dataSource 依然是設置到 Configuration 中。
mappersmappers 對應了具體的 SQL Mapper 文件,也是我們要分析的重點。
mappers 標簽可以多種子標簽,上面的示例中是 mapper 配合 resource:
我們下面看一下此種形式在源碼中的解析:
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"); // 這個分支解析 resource 形式的標簽 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); // 創建 XMLMapperBuilder 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."); } } } } }
resource 標簽解析的對應分支是 (resource != null && url == null && mapperClass == null),其中創建了一個 XMLMapperBuilder 對象然后調用 parse 方法進行解析:
public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析 mapper 下面的標簽,包括 namespace、cache、parameterMap、resultMap 等 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { // namespace 對應 Mapper 對應接口的全名(包名 + 類名) String namespace = context.getStringAttribute("namespace"); builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); // 解析生成 ParameterMap parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析生成 ResultMap resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 每一個 sql 語句生成一個 MappedStatement buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e); } }
configurationElement 用于解析具體的子標簽,如 namespace、cache、parameterMap、resultMap 以及 select|insert|update|delete 等。
namespace 對應了 Mapper 接口類的包名 + 類名,通過 namespace 可以唯一定位一個 Class 文件,解析的 namespace 保存在 builderAssistant 中,后面會用到。
parameterMap 和 resultMap 解析會生成 ParameterMap 和 ResultMap 對象。每個 SQL 語句解析會生成 MappedStatement。
在上面的 parse 方法中,解析完標簽后調用了 bindMapperForNamespace,這個實現了加載 namespace 對應的 Class,并且為每個 Class 創建了代理類工廠對象(MapperProxyFactory)。
MapperProxyFactoryMapperProxyFactory 用于為 Mapper 接口類創建代理對象,代理對象指的是
BlogMapper mapper = session.getMapper(BlogMapper.class) 生成的對象。
下面從 bindMapperForNamespace 開始:
private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class> boundType = null; try { // 加載類 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); // 添加 mapper 和 MapperProxyFactory configuration.addMapper(boundType); } } } }
其中先從 builderAssistant 取出 namespace,然后加載對應的 Class(boundType = Resources.classForName(namespace))。最后調用 configuration.addMapper(boundType) 添加到 configuration 中。 configuration.addMapper(boundType) 很關鍵,看代碼:
publicvoid addMapper(Class type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 添加到 Map , MapperProxyFactory>> 中 knownMappers.put(type, new MapperProxyFactory (type)); // It"s important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won"t try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
關鍵的一行是 knownMappers.put(type, new MapperProxyFactory
protected T newInstance(MapperProxymapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
從中可以看出,這里用的是 Java 的動態代理,Proxy.newProxyInstance 方法生成指定接口的代理對象,這個方法的第三個參數是用于方法攔截的對象,這里是 MapperProxy 的實例。
由此可以知道,具體的執行 SQL 語句的操作是由這個類攔截并且執行的,看看這個類的 invoke 方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
如果是 Object 類中聲明的方法,則直接執行,否則調用 MapperMethod 的 execute,其中便是 JDBC 相關的邏輯了。限于篇幅,具體內容留到下一篇文章再看。
在分析完配置文件的解析后,再回到 XMLConfigBuilder 中:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 構建 SqlSessionFactory 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. } } }
在 parser.parse() 執行完后,生成一個 Configuration 對象,最后調用 build 構建 SqlSessionFactory,代碼如下:
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
可以看到,最終創建的是 DefaultSqlSessionFactory,這個類內部持有 Configuration,并且提供了多個重載的 openSession 方法用于創建 SqlSession 。
到這里,初始化部分就結束了。
總結MyBatis 的初始化流程主要是解析配置文件,將相關信息保存在 Configuration 中,同時對每個 namespace 代表的 Class 生成代理對象工廠。最后,利用 Configuration 生成了一個 DefaultSqlSessionFactory,通過這個對象可以創建 SqlSession 執行 SQL 請求,相關內容將在下一篇(MyBatis 源碼解析(二):SqlSession 執行流程)分析。
如果我的文章對您有幫助,不妨點個贊支持一下(^_^)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72677.html
摘要:我認為學習框架源碼分為兩步抓住主線,掌握框架的原理和流程理解了處理思路之后,再去理解面向對象思想和設計模式的用法目前第一步尚有問題,需要多走幾遍源碼,加深下理解,一起加油 這篇文章我們來深入閱讀下Mybatis的源碼,希望以后可以對底層框架不那么畏懼,學習框架設計中好的思想; 架構原理 架構圖 showImg(https://segmentfault.com/img/remote/...
摘要:簡介上一篇文章源碼解析一初始化和動態代理分析了解析配置文件以及動態代理相關的源碼,這一篇接著上一篇探究的執行流程,另外了解一下中的緩存。總結本文主要分析了的執行流程,結合上一篇文章基本了解了的運行原理。 簡介 上一篇文章(MyBatis 源碼解析(一):初始化和動態代理)分析了 MyBatis 解析配置文件以及 Mapper 動態代理相關的源碼,這一篇接著上一篇探究 SqlSessio...
摘要:的解析和運行原理構建過程提供創建的核心接口。在構造器初始化時會根據和的方法解析為命令。數據庫會話器定義了一個對象的適配器,它是一個接口對象,構造器根據配置來適配對應的對象。它的作用是給實現類對象的使用提供一個統一簡易的使用適配器。 MyBatis的解析和運行原理 構建SqlSessionFactory過程 SqlSessionFactory提供創建MyBatis的核心接口SqlSess...
摘要:基本綱要組成動態配置配置核心源碼分析源碼解析源碼解析源碼解析源碼解析手寫框架是什么本質是一種半自動的框架,前身是其源于和的組合,除了和映射關系之外,還需要編寫語句映射三要素映射規則快速入門加入的依賴添加的配置文件場景介紹編寫實體類接口以及文 showImg(https://segmentfault.com/img/bVblrnC); Mybatis基本綱要 Mybatis組成 · 動態...
摘要:從源碼的角度分析源碼分析從哪一步作為入口呢如果是看過我之前寫的那幾篇關于的源碼分析,我相信你不會在源碼前磨磨蹭蹭,遲遲找不到入口。 微信公眾號「后端進階」,專注后端技術分享:Java、Golang、WEB框架、分布式中間件、服務治理等等。 老司機傾囊相授,帶你一路進階,來不及解釋了快上車! 坐在我旁邊的鐘同學聽說我精通Mybatis源碼(我就想不通,是誰透漏了風聲),就順帶問了我一個...
閱讀 3652·2021-09-02 15:11
閱讀 4563·2021-08-16 10:47
閱讀 1560·2019-08-29 18:35
閱讀 3030·2019-08-28 17:54
閱讀 2843·2019-08-26 11:37
閱讀 1496·2019-08-23 16:51
閱讀 1799·2019-08-23 14:36
閱讀 1801·2019-08-23 14:21