*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SimpleExecutor
前言
筆者大概是從今年的5月份開始喜歡上源碼閱讀的,起初是閱讀徐郡明前輩的《Mybatis技術內幕》入的坑,不得不說大佬就是大佬,書中講得東西很細很全。半年過去了,筆者對mybatis略知一二,也開始在為公司搭架構,并且基于Mybatis寫了一套框架,但是盡管如此還是感覺自己對于源碼的理解稍微有點淺。好比是初高中學數學吧,光看例題不做題是記不住的,因此產生了為mybatis寫注釋的想法,想要通過寫注釋的過程,加強對mybatis的理解。雖然現在網上已經有了較全的mybatis中文注釋,但是感覺還是經過自己手敲更能加強記憶,因此便挖下了這個大坑。筆者也希望可以在一年內把這個坑填完,后續關于其他技術的文章可能就比較少,大多數應該就都是mybatis源碼閱讀犀利了
在這里,附上我的碼云地址(別問我為什么是碼云而不是github,下半天代碼下不動急死人)
mybatis中文注釋
同時,我也很歡迎更多的初中級開發者投入到閱讀源碼的行列,并且很樂意大家在我的倉庫上建立自己的分支,希望可以和大家一同進步。
入口
Mybatis
初始化入口文件是SqlSessionFactoryBuilder。該類通過調用XMLConfigBuilder.parse方法初始化配置文件。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {// 讀取配置文件XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}
在XMLConfigBuilder.parse方法中,會先校驗配置文件是否已經解析過了,如果重復解析就拋出異常
public Configuration parse() {if (parsed) {// 已經解析過就不再解析。這里只解析一次throw new BuilderException("每個 XMLConfigBuilder 只能使用一次.");}parsed = true;// 獲取configuration節點進行解析// mybatis解析配置文件使用的是XPathParser,這里的evalNode方法就是解析xml的代碼// 這里對XPathParser不進行注釋,這不屬于mybatis的范疇(其實是懶。)parseConfiguration(parser.evalNode("/configuration"));return configuration;}
parseConfiguration方法中,傳入configuration節點配置,對mybatis-config.xml文件中的該節點進行解析。解析結果會set到Configuration類中。今天只注釋完了properties和settings兩個節點的解析
/*** 解析mybatis-config.xml文件** @param root*/private void parseConfiguration(XNode root) {try {// 解析properties節點。該節點用來引入外部的資源文件,如db.propertiespropertiesElement(root.evalNode("properties"));// 解析settings節點,校驗配置中的配置項是否合法。該節點用來設置一些mybatis的配置項Properties settings = settingsAsProperties(root.evalNode("settings"));// 加載用戶自己配置的虛擬文件系統loadCustomVfs(settings);// 加載日志loadCustomLogImpl(settings);// TODO 解析typeAliases節點,下次繼續typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));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);}}
先看propertiesElement方法,該方法用于解析properties節點。
/*** 解析mybatis-config.xml的properties節點* 將節點中所有的配置set到Configuration中** @param context* @throws Exception*/private void propertiesElement(XNode context) throws Exception {if (context != null) {// 解析拿到節點下的所有子節點配置Properties defaults = context.getChildrenAsProperties();// 獲取properties節點的resource屬性String resource = context.getStringAttribute("resource");// 獲取properties節點的url屬性。String url = context.getStringAttribute("url");if (resource != null && url != null) {// resource和url屬性只能同時存在一個。throw new BuilderException("properties節點不能同時指定resource和url兩個屬性.");}// url和resource屬性只能同時存在一個// 讀取引入的資源文件所有屬性,put到properties節點之下if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}Properties vars = configuration.getVariables();// 如果configuration之前已經有了配置,也put進去// put這些設置是為了能夠保證后面set回configuration時可以set所有的配置if (vars != null) {defaults.putAll(vars);}parser.setVariables(defaults);// 將Properties節點下所有的配置set到configurationconfiguration.setVariables(defaults);}}
接著就是解析settings節點,該節點用于配置一些mybatis配置項
/*** 解析settings節點** @param context* @return*/private Properties settingsAsProperties(XNode context) {if (context == null) {return new Properties();}// 獲取settings節點下所有的setting節點Properties props = context.getChildrenAsProperties();// 通過Configuration獲取metaClass,用于方便對Configuration進行操作MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);for (Object key : props.keySet()) {// 遍歷setting配置// 如果Configuration沒有這個set方法,說明該配置是無效的if (!metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("配置 " + key + " 無效. 請檢查拼寫是否正確.");}}// 校驗完settings之后返回return props;}
解析完settings節點后,程序會加載用戶配置的虛擬文件系統和日志。
/*** 加載用戶自己設置的虛擬文件系統** @param props* @throws ClassNotFoundException*/private void loadCustomVfs(Properties props) throws ClassNotFoundException {// 從settings中拿到name是vfsImpl的配置節點String value = props.getProperty("vfsImpl");if (value != null) {String[] clazzes = value.split(",");for (String clazz : clazzes) {if (!clazz.isEmpty()) {@SuppressWarnings("unchecked")Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);// 加載文件系統,set到Configuration中configuration.setVfsImpl(vfsImpl);}}}}/*** 加載日志。代碼比較簡單* 就是從settings中拿到name為logImpl的配置項* 然后set到Configuration中去** @param props*/private void loadCustomLogImpl(Properties props) {Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));configuration.setLogImpl(logImpl);}
引申
上面的代碼中使用到了MetaClass類和Configuration類。下面對這兩個類進行解釋。
首先是Configuration類。該類通過名稱可以很明顯的知道這是mybatis的配置類,對應的是mybatis-config.xml文件的配置。其中今天將properties和settings節點對應的字段進行注釋。
public class Configuration {/*** mybatis-config.xml屬性* settings節點* 允許嵌套語句中使用分頁*/protected boolean safeRowBoundsEnabled;/*** mybatis-config.xml屬性* settings節點* 是否開啟自動駝峰命名規則映射* 即從經典數據庫列名 a_column 到經典 Java 屬性名 aColumn 的類似映射。*/protected boolean mapUnderscoreToCamelCase;/*** mybatis-config.xml文件下* settings節點* 當啟用時,對任意延遲屬性的調用會使帶有延遲加載屬性的對象完整加載;* 反之,每種屬性將會按需加載。*/protected boolean aggressiveLazyLoading;/*** mybatis-config.xml文件下* settings節點* 是否允許單一語句返回多條結果集*/protected boolean multipleResultSetsEnabled = true;/*** mybatis-config.xml文件* settings節點* 允許 JDBC 支持自動生成主鍵*/protected boolean useGeneratedKeys;/*** mybatis-config.xml文件* settings節點* 使用列標簽代替列名*/protected boolean useColumnLabel = true;/*** mybatis-config.xml文件* settings節點* 該配置影響的所有映射器中配置的緩存的全局開關*/protected boolean cacheEnabled = true;/*** mybatis-config.xml文件* settings節點* 指定當結果集中值為null的時候是否調用映射對象的set方法*/protected boolean callSettersOnNulls;/*** mybatis-config.xml文件* settings節點* 指定MyBatis增加到日志名稱的前綴*/protected String logPrefix;/*** mybatis-config.xml文件* settings節點* 指定MyBatis所用日志的具體實現*/protected Class<? extends Log> logImpl;/*** mybatis-config.xml文件* settings節點* VFS含義是虛擬文件系統;* 主要是通過程序能夠方便讀取本地文件系統、FTP文件系統等系統中的文件資源。* Mybatis中提供了VFS這個配置。* 主要是通過該配置可以加載自定義的虛擬文件系統應用程序* 多個文件系統使用逗號隔開*/protected Class<? extends VFS> vfsImpl;/*** mybatis-config.xml文件* settings節點* mybatis利用本地緩存機制防止循環引用的加速重復嵌套查詢。* 默認是SESSION,這種情況會緩存一個會話中執行的所有查詢* 如果是STATEMENT,本地會話僅用在語句執行上* 對相同的SqlSession的不同調用將不會共享數據*/protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;/*** mybatis-config.xml文件* settings節點* 當沒有為菜蔬提供特定的JDBC類型時* 為空值制定JDBC類型*/protected JdbcType jdbcTypeForNull = JdbcType.OTHER;/*** mybatis-config.xml* settings節點* 指定哪個對象的方法觸發一次延遲加載*/protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));/*** mybatis-config.xml文件* settings節點* 設置超時時間*/protected Integer defaultStatementTimeout;/*** mybatis-config.xml文件* settings節點* 為驅動程序設置提示以控制返回結果的獲取大小*/protected Integer defaultFetchSize;/*** mybatis-config.xml文件* settings節點* 配置默認的執行器。* SIMPLE 就是普通的執行器;* REUSE 執行器會重用預處理語句(PreparedStatements);* BATCH 執行器將重用語句并執行批量更新。*/protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;/*** mybatis-config.xml文件* settings節點* 指定 MyBatis 應如何自動映射列到字段或屬性。* NONE 表示取消自動映射;* PARTIAL 只會自動映射沒有定義嵌套結果集映射的結果集。* FULL 會自動映射任意復雜的結果集*/protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;/*** mybatis-config.xml文件下* properties節點的所有配置* 以及該節點對應的resource和url的所有配置* 在XMLConfigBuilder.propertiesElement方法中進行初始化*/protected Properties variables = new Properties();/*** mybatis-config.xml文件* settings節點屬性* 延遲加載的全局開關。* 當開啟時,所有關聯對象都會延遲加載。* 特定關聯關系中可通過設置fetchType屬性來覆蓋該項的開關狀態*/protected boolean lazyLoadingEnabled = false;/*** mybatis-config.xml文件* settings節點* 指定Mybatis創建具有延遲加載能力對象所用到的代理工廠*/protected ProxyFactory proxyFactory = new JavassistProxyFactory();/*** 將數據庫類型轉換成Java類型*/protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);/*** 存儲掃包得到的別名*/protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();}
而MetaClass是反射工具箱里的一個類。Reflector是Mybatis中反射模塊的基礎,每個Reflector對象都對應一個類,在該對象中緩存了反射操作需要使用的元信息,如:可讀屬性、可寫屬性、get、set方法等。ReflectorFactory接口主要實現了對Reflector對象的創建和緩存。而MetaClass則是對Reflector和reflectorFactory的封裝,使其更方便通過反射去操作一個類。
這里就不帖MetaClass的代碼了,感興趣可以自行閱讀。
結語
今天因為時間充裕所以寫的博客比較清晰,后面可能會因為加班所以博客僅僅是對代碼注釋的復制粘貼,還希望讀者可以諒解。這個坑我會繼續填下去的。
最后需要提一下java里的一個容易被忽視的規范,也是面試、大學考試經常喜歡問的內容。
類中定義的成員變量也稱之為“字段”,而屬性則是指get和set方法。屬性只與方法有關而與字段無關。如一個類中存在getName()和setName(String name)方法,不管該類中有沒有name字段,我們都認為它有name這個屬性。反之如果只有字段name而沒有對應的get/set方法,則該類僅僅是有name這個字段而沒有name屬性。后面對于get/set方法我不會稱之為屬性,但是有必要分清楚這兩個概念。
*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SimpleExecutor