1. 程式人生 > >Java資料持久層

Java資料持久層

# 一、前言 ## 1.持久層 Java資料持久層,其本身是為了實現與資料來源進行資料互動的存在,其目的是通過分層架構風格,進行應用&資料的解耦。 我從整體角度,依次闡述JDBC、Mybatis、MybatisPlus。 前者總是後者的依賴。只有在瞭解前者,才可以更好地學習後者。 ## 2.技術選型 ciwai ,還有Hibernate、SpringData、JPA等。 至於Hibernate作為知名框架,其最大的特點,是支援面向物件的資料管理。但成也蕭何,敗也蕭何。Hibernate的該功能,導致其太重了。而大多數場景下,我們是不需要這個功能的。另外,Hibernate的該功能,使用起來過於複雜。其設計關聯關係&對映關係,帶來了太多複雜度。 SpringData,則是我看好的另一個Spring原生支援。但是目前主流還是Mybatis,其發展&主流的切換,還需要時間。就讓子彈飛一會兒吧。 至於MybatisPlus,是我在工業物聯網公司時所採用的一個技術方案。其符合“約定大於配置”的技術趨勢,減少了Mybatis那樣的配置成本,但是比JPA更加靈活。更棒的是,它支援stream這樣的編碼方式進行Sql支援(錯誤可以在編譯期透出)。但如果是大型公司,個人的建議是,謹慎考慮,再進行使用。拋開技術方面的考量,MybatisPlus雖然是優秀的開源軟體,但其開源社群&軟體管理確實相對過於薄弱。對於大公司的技術生態而言,這是一個不得不重視的風險點。 ## 3.文章脈絡 不過,Mybatis作為現在最流行的ORM框架,還是值得大家相信的。所以經過考慮,這邊文章雖然包含三塊內容,但是JDBC更多作為一個依賴,進行了解。而MybatisPlus主要側重於其核心功能-BaseMapper的實現,以及其擴充套件Mybatis得到的擴充套件實現方式。整篇文章的重點,還是落在Mybatis,對其投入較大的精力進行描述。 ## 4.文章優勢 又到了王婆賣瓜的階段。 文章最大的兩個優點:圖&結構。 本篇文章採用了數十張圖,用於展現對應關係。畢竟一圖勝千言嘛。 而結構方面,文章採用MECE原則。文章分為JDBC、Mybatis、MybatisPlus。核心的Mybatis分為靜態結構&執行流程。靜態結構對Mybatis的架構,以及模組進行了展開。執行流程則是針對Mybatis的初始化&執行兩個重要生命週期節點,進行展開。最後,通過Mybatis的核心Configuration的核心欄位解析(作用、來源、去向)進行總結收納。 ## 5.文章遺憾 遺憾主要集中在兩個方面: * 由於是一個長文(接近6W字),最近事情又多(財年底,大家懂的),所以難免有一些疏漏。歡迎大家指出來哈。 * 戰線拖得太長(寫了快兩個月)。雖然還有很多地方可以展開&深入,但是經過考慮後,還是放棄了。 > 文章中有很多補充部分,大家可以自行查閱,擴充套件知識面。雖然我查詢了一些資料,但是有點整理不動(又不知大家是否感興趣)。當然,如果大家對某部分感興趣,可以提出來,我出個單章。 # 二、JDBC ## 1.簡介 JDBC是一個規範,其分為兩個部分: * 廠商:完成資料庫驅動 * Java開發者:呼叫統一介面 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104151549557.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) ## 2.整體結構 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210105174946627.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) 對應元件: * DriverManager:資料庫驅動管理器 * Driver:資料庫驅動的抽象介面,用於與資料庫服務進行通訊 * Connection:與資料庫的連線 * Statement:用於提交SQL語句 * Statement:通用介面,繼承自Wrapper。普通的不帶參的查詢SQL;支援批量更新,批量刪除; * PreparedStatement:預編譯介面,繼承自Statement。可變引數的SQL,編譯一次,執行多次,效率高; 安全性好,有效防止Sql注入等問題; * CallableStatement:繼承自PreparedStatement。支援呼叫儲存過程,提供了對輸出和輸入/輸出引數(INOUT)的支援; * ResultSet:用於儲存資料庫結果 * SQLException:資料庫異常 ## 3.生命週期 ### a.初始化過程 驅動註冊&配置注入 ### b.執行過程 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104160340338.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) ## 4.程式碼示例 原生JDBC較為原始,架構上的設計也是非常薄的。 所以,說得太多,還不如看看應用程式碼。 ```java // 1. 註冊驅動 // 使用java.sql.DriverManager類的靜態方法registerDriver(Driver driver) // Driver是一個介面,引數傳遞:MySQL驅動程式的實現類 // DriverManager.registerDriver(new Driver()); // 檢視驅動類原始碼,註冊兩次驅動,浪費資源 Class.forName("com.mysql.jdbc.Driver"); // 2. 獲得連線 // uri:資料庫地址 jdbc:mysql://連線主機ip:埠號//資料庫名字 String url = "jdbc:mysql://localhost:3306/TEST"; // static Connection getConnection(String url, String user, String password) // 返回值是java.sql.Connection介面的實現類,在MySQL驅動程式中 Connection conn = DriverManager.getConnection(url, "root", "123456"); // conn.setAutoCommit(false); // 用於事務提交conn.commit(),conn.rollback() System.out.println(conn);// com.mysql.jdbc.JDBC4Connection@10d1f30 // 3. 獲得語句執行平臺,通過資料庫連線物件,獲取到SQL語句的執行者物件 //conn物件,呼叫方法 Statement createStatement() 獲取Statement物件,將SQL語句傳送到資料庫 //返回的是Statement介面的實現類物件,在MySQL驅動程式中 Statement statement = conn.createStatement(); System.out.println(statement);//com.mysql.jdbc.StatementImpl@137bc9 // 4. 執行sql語句 //通過執行者物件呼叫方法執行SQL語句,獲取結果 //int executeUpdate(String sql) 執行資料庫中的SQL語句,僅限於insert,update,delete //返回值int,操作成功資料庫的行數 ResultSet resultSet = statement.executeQuery("SELECT * from user where id = 1"); System.out.println(resultSet); // 5. 釋放資源 statement.close(); conn.close(); ``` ## 5.總結 關鍵詞:簡單、原始、看不到 現在基本沒有人直接使用了。大多使用框架。我在生產級的使用,還是剛工作的時候,在前端使用了類似的東東。 # 三、Mybatis ## 1.整體框架 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210104160457688.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) 對應模組: * 介面層 * SqlSession:應用程式與Mybatis的互動介面 * 核心處理層 * 配置解析:對Mybatis配置檔案、對映檔案,dao介面註解等進行配置解析,生成Configuration物件 * SQL解析:MyBatis 實現動態SQL 語句的功能,並提供了諸如where等SQL語句節點 * 引數對映:根據實參,解析動態SQLL節點,生成可執行SQL語句,處理佔位符,繫結實參 * SQL執行:負責快取,事務,JDBC等的排程。詳見執行過程圖 * 結果集對映:通過ResultSetHandler等,完成結果集的對映,得到結果物件並返回 * 外掛:提供外掛介面,便於使用者擴充套件,甚至修改Mybatis預設行為 * 基礎支援層 * 資料來源模組:通過配置生成(可委託第三方資料來源框架),包含目標資料庫資訊,向上支援連線生成等 * 事務管理模組:對資料庫事務進行抽象,並提供簡單實現。可與Spring整合,由Spring實現事務管理 * 快取模組:為Mybatis的一二級快取提供支援,從而優化資料庫效能 * Binding模組:實現DAO介面檔案與對應對映檔案的關聯 * 反射模組:對Java原生反射進行了封裝與優化 * 型別轉換:一方面實現JavaType與JDBCType的轉換,另一方面支撐Mybatis的別名機制 * 日誌模組:提供詳細日誌輸出資訊,並能夠整合第三方日誌框架(log4j,sel4j等) * 資源載入:封裝Java原生類載入器,提供類與其他資原始檔的有序載入能力 * 解析器模組:一方面封裝XPath,提供xml配置檔案解析能力,另一方面為動態Sql佔位符的處理,提供支援 > 資料來源模組補充:即常用元件-DataSource。MyBatis 自身提供了相應的資料來源實現(Pooled,UnPooled,Jndi),MyBatis 也提供第三方資料來源整合的介面()。現在開源的資料來源都提供了比較豐富的功能,如連線池功能、檢測連線狀態等 > @Select註解,就可以省略對應的對映檔案節點 > DAO介面的實現類,是由Mybatis自動建立的動態代理物件(依賴於對應的對映檔案節點) > Mybatis初始化階段:載入Mybatis配置檔案、對映檔案,dao介面註解->儲存到configuration物件中->建立SqlSessionFactory物件。Mybatis初始化階段後,開發者可以通過SqlSessionFactory,獲取對應的SqlSession。wdk-som的資料庫配置就是直接配置生成DataSource與SqlSessionFactory。 ### a.解析模組 Mybatis的配置,有三種途徑: * XML:如Mybatis-config.xml * 註解:如DAO介面方法上的@Select * 注入:如MybatisConfiguration類 其中,XML是Mybatis配置的主要方式。XML配置則涉及XML檔案解析。 XML常見解析方式,有一下三種: * DOM:前端小夥伴,不要太熟悉。DOM 是基於樹形結構的XML 解析方式,它會將整個XML 文件讀入記憶體並構建一個DOM樹,基於這棵樹形結構對各個節點( Node )進行操作。DOM 解析方式的優點是易於程式設計,可以根據需求在樹形結構的各節點之間導航。DOM 解析方式的缺點是在XML檔案較大時,會造成較大的資源佔用(因為需要構建DOM樹)。 * SAX:SAX 是基於事件模型的XML 解析方式。當SAX 解析器解析到某型別節點時,會觸發註冊在該型別節點上的回撥函式,開發人員可以根據自己感興趣的事件註冊相應的回撥函式。由於在處理過程中井不會在記憶體中記錄XML 中的資料,所以佔用的資源比較小,這是其優點。其缺點是因為不儲存XML 文擋的結構,所以需要開發人員自己負責維護業務邏輯涉及的多層節點之間的關係。 * StAX:StAX將XML 文件作為一個事件流進行處理。不同於SAX,在StAX 解析方式中,應用程式控制著整個解析過程的推進,可以簡化應用處理XML 文件的程式碼,並且決定何時停止解析,而且StAX 可以同時處理多個XML 文件。 而Mybatis則是採用DOM解析方式,並結合XPath進行XML解析。 > DOM 會將整個XML 文件載入到記憶體中並形成樹狀資料結構,而XPath 是一種為查詢XML 文件而設計的語言,可以與DOM 解析方式配合使用,實現對XML 文件的解析。XPath 之於XML 就好比SQL 語言之於資料庫。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210114104822237.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70) org.apache.ibatis.parsing.XPathParser#variables:mybatis-config.xml 中<propteries>標籤定義的鍵位對集合。 XPathParser中提供了一系列的eval*方法用於解析boolean、short、long、int、String、Node等型別的資訊,它通過呼叫XPath.evaluate方法查詢指定路徑的節點或屬性,並進行相應的型別裝換。 剩餘部分,此處不再詳解。 ### b.反射模組 Mybatis執行過程中,大量使用了反射(如生成DAO對應代理實現類)。Mybatis對Java原生的反射操作進行了進一步的封裝,從而提供更加簡潔的API。 Reflector 是MyBatis 中反射模組的基礎,每個Reflector 物件都對應一個類,在Reflector 中快取了反射操作需要使用的類的元資訊。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210114104934817.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) 從上圖中,可以看出 * 核心類: * Reflector:對於每個類,都有一個對應的Reflector,用於儲存其類元資訊。可以類比Spring中的Bean。但是其內部沒有類之間的關聯&依賴關係 * MetaClass:MetaClass 是MyBatis 對類級別的元資訊的封裝和處理。MetaClass 通過Reflector 和PropertyTokenizer 組合使用, 實現了對複雜的屬性表示式的解析,並實現了獲取指定屬性描述資訊的功能。 * MetaObject:ObjectWrapper實現的屬性表示式解析功能,是委託給MetaObject實現的。 * 包: * invoker:包含MethodInvoker、SetFieldInvoker等,用於實現目標方法反射呼叫,屬性讀取與設定等 * factory:包含ObjectFactory&DefaultObjectFactory,物件建立工廠。ObjectFactory提供例項建立介面,其預設實現為DefaultObjectFactory。在Mybatis原始碼的測試類中,存在對應測試。 * property:包含PropertyCopier、PropertyNamer、PropertyTokenizer,是類欄位工具,提供如欄位複製、欄位是否為屬性、欄位與index轉化(屬性表示式&Sql佔位符應用)等功能。 * wrapper:包含ObjectWrapperFactory、ObjectWrapper、BaseWrapper等。ObjectWrapper介面是對物件的包裝,抽象了物件的屬性資訊,定義了一系列查詢物件屬性資訊的方法,以及更新屬性的方法。 > TypeParameterResolver:進行型別解析。如TypeParameterResolver#resolveReturnType會返回對應類&方法的返回型別。在Mybatis原始碼的測試類中,存在對應測試。 #### Reflector 每個類,都有其對應等Reflector,用於儲存其對應的類元資訊(屬性,欄位等) ```java public class Reflector { // 對應的Class 型別 private final Class type; // 可讀屬性的名稱集合 private final String[] readablePropertyNames; // 可寫屬性的名稱集合 private final String[] writablePropertyNames; // 屬性相應的setter方法,key是屬性名稱,value是Invoker物件 private final Map setMethods = new HashMap<>(); // 屬性相應的getter方法集合,key是屬性名稱,value是Invoker物件 private final Map getMethods = new HashMap<>(); // 屬性相應的setter方法的引數值型別,key是屬性名稱,value是setter方法的引數型別 private final Map> setTypes = new HashMap<>(); // 屬性相應的getter方法的返回位型別,key是屬性名稱,value是getter方法的返回位型別 private final Map> getTypes = new HashMap<>(); // 預設構造方法 private Constructor defaultConstructor; // 所有屬性名稱的集合,key是屬性名稱的大寫形式,value是屬性名稱 private Map caseInsensitivePropertyMap = new HashMap<>(); // 構造方法 public Reflector(Class clazz) { type = clazz; addDefaultConstructor(clazz); addGetMethods(clazz); addSetMethods(clazz); addFields(clazz); // 學習一下:Collection.toArray()返回的是Object[],而Collection.toArray(T[] a)返回的是T[] readablePropertyNames = getMethods.keySet().toArray(new String[0]); writablePropertyNames = setMethods.keySet().toArray(new String[0]); for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } } // 其他方法 } ``` > 上述提到的都是”屬性“,而不是欄位。按照JavaBean的規範,類中定義的成員變數稱為“ 宇段” ,屬性則是通過ge陽r/setter 方法得到的,屬性只與類中的方法有關,與是否存在對應成員變數沒有關係。 > 所以,Mybatis與對應DO進行互動的依據是getter/setter方法。所以,可以通過自定義getter/setter方法進行欄位轉換。另外,DO中有欄位,但沒有對應getter/setter方法,則無法在對應mapper進行對映,最終導致報錯。 #### MetaClass MetaClass 是MyBatis 對類級別的元資訊的封裝和處理。 MetaClass 通過Reflector 和PropertyTokenizer 組合使用, 實現了對複雜的屬性表示式的解析,並實現了獲取指定屬性描述資訊的功能。 ```java /** * @author Clinton Begin */ public class MetaClass { private final ReflectorFactory reflectorFactory; // class對應等Reflector private final Reflector reflector; private MetaClass(Class type, ReflectorFactory reflectorFactory) { this.reflectorFactory = reflectorFactory; this.reflector = reflectorFactory.findForClass(type); } // 核心方法:解析屬性表示式。委託給buildProperty方法實現 public String findProperty(String name) { StringBuilder prop = buildProperty(name, new StringBuilder()); return prop.length() > 0 ? prop.toString() : null; } private StringBuilder buildProperty(String name, StringBuilder builder) { // name即是屬性表示式。如
// PropertyTokenizer包含name、indexName、index、children PropertyTokenizer prop = new PropertyTokenizer(name); // 判斷是否還有子表示式 if (prop.hasNext()) { String propertyName = reflector.findPropertyName(prop.getName()); if (propertyName != null) { // 返回結果,追加屬性名(.name形式) builder.append(propertyName); builder.append("."); // 為該屬性,建立對應的MetaClass MetaClass metaProp = metaClassForProperty(propertyName); // 深度優先遞迴。建立所有MetaClass,並通過builder形成一個深度優先遍歷的關係鏈 metaProp.buildProperty(prop.getChildren(), builder); } } else { String propertyName = reflector.findPropertyName(name); if (propertyName != null) { builder.append(propertyName); } } return builder; } } ``` #### ObjectWrapper ObjectWrapper介面是對物件的包裝,抽象了物件的屬性資訊,定義了一系列查詢物件屬性資訊的方法,以及更新屬性的方法。 其功能實現,是通過實現基礎類-BaseObjectWrapper,委託給MetaObject實現。 ```java /** * @author Clinton Begin */ public interface ObjectWrapper { // 如採Object Wrapper 中封裝的是普通的Bean物件,則呼叫相應屬性的相應getter 方法 // 如採封裝的是集合類,則獲取指定key或下標對應的value值 Object get(PropertyTokenizer prop); void set(PropertyTokenizer prop, Object value); // 查詢屬性表示式指定的屬性,第二個引數表示是否忽略屬性表示式中的下畫線 String findProperty(String name, boolean useCamelCaseMapping); String[] getGetterNames(); String[] getSetterNames(); // 解析屬性表示式指定屬性的setter 方法的引數型別。name為請求的屬性表示式 Class getSetterType(String name); Class getGetterType(String name); boolean hasSetter(String name); boolean hasGetter(String name); // 為屬性表示式指定的屬性建立相應的MetaObject物件 MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory); boolean isCollection(); void add(Object element); void addAll(List element); } ``` #### MetaObject ObjectWrapper實現的屬性表示式解析功能,是委託給MetaObject實現的。 ```java /** * @author Clinton Begin */ public class MetaObject { // 原生物件,即MetaObject所表示的物件 private final Object originalObject; private final ObjectWrapper objectWrapper; private final ObjectFactory objectFactory; private final ObjectWrapperFactory objectWrapperFactory; private final ReflectorFactory reflectorFactory; private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) { this.originalObject = object; this.objectFactory = objectFactory; this.objectWrapperFactory = objectWrapperFactory; this.reflectorFactory = reflectorFactory; // 根據物件型別,使用不同的wrapper方法 if (object instanceof ObjectWrapper) { this.objectWrapper = (ObjectWrapper) object; } else if (objectWrapperFactory.hasWrapperFor(object)) { this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object); } else if (object instanceof Map) { this.objectWrapper = new MapWrapper(this, (Map) object); } else if (object instanceof Collection) { this.objectWrapper = new CollectionWrapper(this, (Collection) object); } else { this.objectWrapper = new BeanWrapper(this, object); } } public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) { if (object == null) { return SystemMetaObject.NULL_META_OBJECT; } else { return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory); } } // 從MetaObject中,獲取某個欄位的屬性值 public Object getValue(String name) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { return null; } else { return metaValue.getValue(prop.getChildren()); } } else { return objectWrapper.get(prop); } } // 對MetaObject中某個欄位進行賦值 public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value); } else { objectWrapper.set(prop, value); } } // 其他方法 ``` ### c.型別轉換 JDBC 資料型別與Java 語言中的資料型別井不是完全對應的,所以在PreparedStatement 為SQL 語句繫結引數時,需要從Java 型別轉換成JDBC 型別,而從結果集中獲取資料時,則需要從JDBC 型別轉換成Java 型別。My Batis 使用型別模組完成上述兩種轉換。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/202101151146268.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) #### TypeHandler ```java /** * @author Clinton Begin */ public interface TypeHandler { // 設定引數。在通過PreparedStatement 為SQL 語句繫結引數時,會將資料由Java 型別轉換成JdbcType 型別 // 《Mybatis技術內幕》這部分的解釋反了,詳見入參與功能實現程式碼 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; // 獲取結果。 // 從ResultSet 中獲取資料時會呼叫此方法,會將資料由Java 型別轉換成JdbcType 型別 T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; } ``` #### BaseTypeHandler ```java public abstract class BaseTypeHandler extends TypeReference implements TypeHandler { @Deprecated protected Configuration configuration; @Deprecated public void setConfiguration(Configuration c) { this.configuration = c; } @Override public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { if (parameter == null) { if (jdbcType == null) { throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); } try { ps.setNull(i, jdbcType.TYPE_CODE); } catch (SQLException e) { throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + "Cause: " + e, e); } } else { try { // 設定引數,該方法具體有子類實現 setNonNullParameter(ps, i, parameter, jdbcType); } catch (Exception e) { throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + "Try setting a different JdbcType for this parameter or a different configuration property. " + "Cause: " + e, e); } } } @Override public T getResult(ResultSet rs, String columnName) throws SQLException { try { return getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e); } } @Override public T getResult(ResultSet rs, int columnIndex) throws SQLException { try { return getNullableResult(rs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e); } } @Override public T getResult(CallableStatement cs, int columnIndex) throws SQLException { try { return getNullableResult(cs, columnIndex); } catch (Exception e) { throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e); } } public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException; public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException; public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException; } ``` 實現子類的型別轉換,最終還是會落到JDBC的PreparedStatement/ResultSet中對應的型別轉換方法。 而PreparedStatement/ResultSet,是由入參帶入的。 TypeHandlerRegistry&TypeAliasRegistry,主要是進行型別處理器&類型別名的管理(類似IOC容器對Bean的管理)。 ### d.資料來源模組 Mybatis的資料來源模組,採用了工廠方法設計模式。 如其中DataSourceFactory是工廠介面,而PooledDataSourceFactory等則是其工廠實現類。 Mybatis提供了三個工廠類實現方式: * PooledDataSourceFactory * UnpooledDataSourceFactory * JndiDataSourceFactory ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210201163027804.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) 呼叫方舉例:org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement #### DataSourceFactory ```java public interface DataSourceFactory { void setProperties(Properties var1); DataSource getDataSource(); } ``` #### UnpooledDataSourceFactory ```java public class UnpooledDataSourceFactory implements DataSourceFactory { private static final String DRIVER_PROPERTY_PREFIX = "driver."; private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); protected DataSource dataSource; public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } @Override public void setProperties(Properties properties) { Properties driverProperties = new Properties(); // 利用基礎層的配置解析模組,建立DataSource 相應的MetaObject MetaObject metaDataSource = SystemMetaObject.forObject(dataSource); for (Object key : properties.keySet()) { // 遍歷Properties,從而獲取DataSource所需的配置資訊 String propertyName = (String) key; // 以”driver.”開頭的配置項,是對DataSource的配置,記錄到driverProperties中儲存 if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { String value = properties.getProperty(propertyName); driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); } else if (metaDataSource.hasSetter(propertyName)) { String value = (String) properties.get(propertyName); Object convertedValue = convertValue(metaDataSource, propertyName, value); metaDataSource.setValue(propertyName, convertedValue); } else { throw new DataSourceException("Unknown DataSource property: " + propertyName); } } if (driverProperties.size() > 0) { metaDataSource.setValue("driverProperties", driverProperties); } } @Override public DataSource getDataSource() { return dataSource; } private Object convertValue(MetaObject metaDataSource, String propertyName, String value) { Object convertedValue = value; Class targetType = metaDataSource.getSetterType(propertyName); if (targetType == Integer.class || targetType == int.class) { convertedValue = Integer.valueOf(value); } else if (targetType == Long.class || targetType == long.class) { convertedValue = Long.valueOf(value); } else if (targetType == Boolean.class || targetType == boolean.class) { convertedValue = Boolean.valueOf(value); } return convertedValue; } } ``` #### UnpooledDataSource ```java public class UnpooledDataSource implements DataSource { // 進行驅動載入的classLoader,可參照JDBC相關處理 private ClassLoader driverClassLoader; // 驅動配置 private Properties driverProperties; // 驅動登錄檔(全量) private static Map registeredDrivers = new ConcurrentHashMap<>(); // 當前DataSource所採用的驅動,如mysqlDriver private String driver; // 資料來源地址 private String url; // 使用者名稱 private String username; // 密碼 private String password; // 是否自動提交(有關於事務),預設自動提交 private Boolean autoCommit; // 預設事務隔離級別 private Integer defaultTransactionIsolationLevel; // 預設網路超時時間 private Integer defaultNetworkTimeout; // 驅動註冊 static { Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } // 方法略 } ``` ### e.事務管理模組 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210201180042632.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) #### TransactionFactory ```java public interface TransactionFactory { default void setProperties(Properties props) { // NOP } Transaction newTransaction(Connection conn); Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); } ``` #### Transaction ```java public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; // 獲取事務超時時間(Spring的SpringManagedTransaction,存在對應實現) Integer getTimeout() throws SQLException; } ``` #### SpringManagedTransaction ```java public class SpringManagedTransaction implements Transaction { private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class); private final DataSource dataSource; private Connection connection; // 當前連線是否為事務連線 private boolean isConnectionTransactional; // 是否自動提交。如果是自動提交,也就不需要手動commit()了 private boolean autoCommit; public SpringManagedTransaction(DataSource dataSource) { Assert.notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; } public Connection getConnection() throws SQLException { if (this.connection == null) { this.openConnection(); } return this.connection; } private void openConnection() throws SQLException { this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); // DataSourceUtils獲取對應的事務性ConnectionHolder,然後比對當前連線與ConnectionHolder this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } } public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Committing JDBC Connection [" + this.connection + "]"); } // 事務提交 this.connection.commit(); } } public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]"); } // 事務回滾 this.connection.rollback(); } } public void close() throws SQLException { // 通過DataSourceUtils,釋放當前連線。依舊涉及ConnectionHolder DataSourceUtils.releaseConnection(this.connection, this.dataSource); } public Integer getTimeout() throws SQLException { // Connection沒有對應的事務超時時間,這裡直接呼叫底層實現,獲取事務超時時間 ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource); return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null; } } ``` 這裡的實現,涉及Connection的事務實現、DataSourceUtils、TransactionSynchronizationManager.getResource三個點。 ### f.快取模組 Cache:多種實現。如FIFO、LRU CacheKey:應對SQL的可變引數 TransactionalCacheManager&TransactionalCache:事務快取 快取模組,是直接關聯執行模組-Executor模組 * Mybatis的快取: * 一級快取:預設開啟。屬於SqlSession級別的快取。利用BaseExecute -> PerpetualCache -> HashMap實現。 * 二級快取:預設關閉。屬於全域性級別的快取。利用CacheExecute -> TransactionalCacheManager -> HashMap -> TransactionalCache 快取實現,採用裝飾器模式 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210202170729700.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210202182132203.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70) #### Cache ```java public interface Cache { String getId(); void putObject(Object key, Object value); Object getObject(Object key); Object removeObject(Object key); void clear(); int getSize(); default ReadWriteLock getReadWriteLock() { return null; } } ``` #### PerpetualCache ```java public class PerpetualCache implements Cache { private final String id; private final Map cache = new HashMap<>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } @Override public Object removeObject(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); } @Override public boolean equals(Object o) { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } if (this == o) { return true; } if (!(o instanceof Cache)) { return false; } Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } @Override public int hashCode() { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } return getId().hashCode(); } } ``` #### SynchronizedCache ```java public class SynchronizedCache implements Cache { private final Cache delegate; public SynchronizedCache(Cache delegate) { this.delegate = delegate; } @Override public String getId() { return delegate.getId(); } @Override public synchronized int getSize() { return delegate.getSize(); } @Override public synchronized void putObject(Object key, Object object) { delegate.putObject(key, object); } @Override public synchronized Object getObject(Object key) { return delegate.getObject(key); } @Override public synchronized Object removeObject(Object key) { return delegate.removeObject(key); } @Override public synchronized void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } } ``` #### CacheKey ```java public class CacheKey implements Cloneable, Serializable { private static final long serialVersionUID = 1146682552656046210L; public static final CacheKey NULL_CACHE_KEY = new CacheKey() { @Override public void update(Object object) { throw new CacheException("Not allowed to update a null cache key instance."); } @Override public void updateAll(Object[] objects) { throw new CacheException("Not allowed to update a null cache key instance."); } }; private static final int DEFAULT_MULTIPLIER = 37; private static final int DEFAULT_HASHCODE = 17; private final int multiplier; private int hashcode; private long checksum; private int count; private List updateList; public CacheKey() { this.hashcode = DEFAULT_HASHCODE; this.multiplier = DEFAULT_MULTIPLIER; this.count = 0; this.updateList = new ArrayList<>(); } public CacheKey(Object[] objects) { this(); updateAll(objects); } public int getUpdateCount() { return updateList.size(); } public void update(Object object) { int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); } public void updateAll(Object[] objects) { for (Object o : objects) { update(o); } } @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof CacheKey)) { return false; } final CacheKey cacheKey = (CacheKey) object; if (hashcode != cacheKey.hashcode) { return false; } if (checksum != cacheKey.checksum) { return false; } if (count != cacheKey.count) { return false; } for (int i = 0; i < updateList.size(); i++) { Object thisObject = updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (!ArrayUtil.equals(thisObject, thatObject)) { return false; } } return true; } @Override public int hashCode() { return hashcode; } @Override public String toString() { StringJoiner returnValue = new StringJoiner(":"); returnValue.add(String.valueOf(hashcode)); returnValue.add(String.valueOf(checksum)); updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add); return returnValue.toString(); } @Override public CacheKey clone() throws CloneNotSupportedException { CacheKey clonedCacheKey = (CacheKey) super.clone(); clonedCacheKey.updateList = new ArrayList<>(updateList); return clonedCacheKey; } } ``` ### g.Binding模組 在mybatis的前身-iBatis,資料插入是這樣的: ```java sqlSession.insert("insert", userDO); ``` 或者,抽象一下: ```java public interface UserDAO { void insertUser(UserDO userDO); } public class UserDAOImpl extends SqlMapDaoTemplate implements UserDAO { public UserDAOImpl(DaoManager daoManager) { super(daoManager); } public void insertUser(UserDO userDO) throws SQLException { insert("insert", userDO); } } ``` 兩個實現,都涉及一個問題,需要手寫 ```java insert("insert", userDO); ``` 那麼寫錯,也是完全可能的嘛。但iBatis這部分,與Mybatis一樣,是通過執行時的反射實現的。那麼就無法快速失敗,從而在啟動時檢索出問題。 如果一個不常用的方法實現的入參方法名寫錯了。Boom,線上故障+緊急釋出。 所以,這裡需要一個解決方案,可以在啟動時,就檢索出對應錯誤。 Mybatis給出的答案是,不再需要寫上述實現。**Mybatis直接通過Binding模組,直接關聯DAO&對應Mapper。如果對映存在問題,則在啟動時丟擲相應問題。** 舉個栗子,如果在DAO的入參中沒有String shopCode,而對應Mapper有對應入參注入,則會在啟動時報錯,提示“無法找到對應入參”。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210202111952716.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) #### MapperRegistry ```java public class MapperRegistry { // Mybatis全域性Configuration,通過構造器注入 private final Configuration config; // mapperInterface與相應MapperProxyFactory的對映表 // 如果是sqlSession.xxx的使用,則不經過這裡。因為sqlSession在執行過程中,屬於更底層的位置。詳見後文:生命週期-執行過程 private final Map, MapperProxyFactory> knownMappers = new HashMap<>(); public MapperRegistry(Configuration config) { this.config = config; } // 通過mapperInterface,獲取對應的MapperProxy(type為介面型別) @SuppressWarnings("unchecked") public T getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } public boolean hasMapper(Class type) { return knownMappers.containsKey(type); } // 初始化過程中,用於新增mapperInterface。詳見下述生命週期-初始化 public void addMapper(Class type) { // 檢測type是否為介面型別,因為是針對mapperInterface if (type.isInterface()) { // 判斷該介面是否已經注入到上面的對映表knownMappers中 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 進行對應mapper的解析,詳見下述生命週期-初始化 knownMappers.put(type, new MapperProxyFactory<>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { // 失敗,“回滾” if (!loadCompleted) { knownMappers.remove(type); } } } } // 其他方法 } ``` #### MapperProxyFactory ```java public class MapperProxyFactory { private final Class mapperInterface; // 該介面中,method與對應Invoker的對映表。 // MapperMethodInvoker與MapperMethod關係,詳見org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker private final Map methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } public Class getMapperInterface() { return mapperInterface; } public Map getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy mapperProxy) { 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); } } ``` #### MapperProxy ```java public class MapperProxy implements InvocationHandler, Serializable { // 核心欄位 // 關聯的SqlSession private final SqlSession sqlSession; // 當前Mapper,所對應的mapperInterface private final Class mapperInterface; // 當前Mapper中,Method與Invoker對應的對映表,作為快取。此是由MapperProxyFactory給出 private final Map methodCache; // 核心方法 public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如採目標方法繼承自Object ,則直接呼叫目標方法。如toString()等方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 其他的方法,則是Mapper相關的方法(非Object方法),則需要通過MapperMethodInvoker。具體可參照下面的PlainMethodInvoker return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { MapperMethodInvoker invoker = methodCache.get(method); if (invoker != null) { return invoker; } return methodCache.computeIfAbsent(method, m -> { // 預設方法是公共非抽象的例項方法。也就是Interface的default方法 if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { // 根據預設方法的判定,常用的MapperMethodInvoker是PlainMethodInvoker return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } // 核心內部類 private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { super(); this.mapperMethod = mapperMethod; } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { // 通過MapperMethod.execute(),進行Sql語句的代理執行。詳見MapperMethod return mapperMethod.execute(sqlSession, args); } } } ``` #### MapperMethod MapperMethod 中封裝了Mapper 介面中對應方法的資訊,以及對應SQL 語句的資訊 MapperMethod 物件。MapperMethod 物件會完成引數轉換以及SQL語句的執行功能 MapperMethod 中並不記錄任何狀態相關的資訊,所以可以在多個代理物件之間共享 ```java public class MapperMethod { // 當前Mapper下Method的Sql資訊(SqlCommand) // SqlCommand包含SQL語句的名稱和型別 private final SqlCommand command; // 當前Mapper下Method的方法簽名,包括入參與返回值(型別&位置資訊等) private final MethodSignature method; public MapperMethod(Class mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } // 核心方法 public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根據SqlCommand的型別,執行不同的分支 switch (command.getType()) { case INSERT: { // 引數關聯,將傳入實引數與方法形參關聯起來。通過MethodSIgnature下的convertArgsToSqlCommandParam(),間接呼叫ParamNameResolver.getNamedParams(),從而獲取Map Object param = method.convertArgsToSqlCommandParam(args); // 通過sqlSession.insert(command.getName(), param)進行執行,並將其返回值(effectLines),按照當前Method的返回值,返回對應型別的值(int、long、boolean) result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // 根據返回值型別不同,呼叫不同執行方法,並返回不同結果 // 但其中executexxx()本質,還是呼叫sqlSession.xxx(),獲取執行結果 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { // 這部分,個人認為也可以採用一個私有方法進行處理。 // 這裡為什麼不作為私有方法處理。個人猜測:一方面是命名(命名與語義關聯);另一方面是為了更直觀展示other的處理方式,提高程式碼可讀性? Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } ``` #### 小結: MapperRegistry.getMapper -> MapperProxyFactory.newInstance -> MapperProxy.invoke -> MapperMethod.execute -> Sqlsession.xxx(進入執行時) ### h.資源載入模組 (暫略) ### i.日誌模組 (暫略) ## 2.生命週期 ### a.初始化過程 Mybatis初始化 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210120162006246.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) Mybatis初始化-解析Mapper ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210120162031449.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) mapper解析過程中,存在*incompile 與 parsePending*,很有意思。與 對MyBatis 初始化過程的分析可知, 對映配置檔案中定義的SQL 節點會被解析成MappedStatement物件, 其中的SQL 語句會被解析成SqlSource 物件, SQL 語句中定義的動態SQL 節點、文字節點等,則由SqlNode 介面的相應實現表示。 MappedStatement包含SqlSource sqlSource。 SqlSource實現:DynamicSqlSource 負責處理動態SQL 語句,RawSqlSource 負責處理靜態語句,兩者最終都會將處理後的SQL 語句封裝成StaticSqlSource返回。 DynamicSqlSource 與StaticSq!Source 的主要區別是:StaticSq!Source 中記錄的SQL語句中可能含有“?”佔位符,但是可以直接提交給資料庫執行:DynamicSqlSourc e 中封裝的SQL語句還需要進行一系列解析,才會最終形成資料庫可執行的SQL 語句。 MyBatis 在處理動態SQL節點時,應用到了組合設計模式。MyBatis 會將動態SQL節點解析成對應的SqINode 實現,並形成樹形結構。 DynamicContext 主要用於記錄解析動態SQL 語句之後產生的SQL 語句片段,可以認為它是一個用於記錄動態SQL 語句解析結果的容器。 SqlNode 介面有多個實現類,每個實現類對應一個動態SQL節點。如IfSqlNode,表示mapper對映檔案中的if節點。 #### MapperBuilderAssistant ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210308110748427.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) 從上圖,就可以看出MapperBuilderAssistant這個類的實際地位了。 BaseBuilder作為一個抽象類,提供了一個構建規約。 MapperBuilderAssistant則是實際提供構建能力的assistant。 而XMLStatementBuilder、XMLMapperBuilder等構建器,都是通過組合的方式,將通用能力,委託於MapperBuilderAssistant。 這個部分,這裡只是點一下。 ### b.執行過程 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210120180955497.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) > SQL 語句的執行涉及多個元件,其中比較重要的是Executor 、StatementHandler 、ParameterHandler 和ResultSetHandler 。 > Executor主要負責維護一級快取和二級快取,並提供事務管理的相關操作,它會將資料庫相關操作委託給StatementHandler完成。 > StatementHandler先通過ParameterHandler 完成SQL語句的實參繫結。然後通過java.sql.Statement 物件執行SQL 語句並得到結果集。最後通過ResultSetHandler 完成結果集的對映,得到結果物件並返回。 > Mybatis最終是直接通過DataSource.getConnection(),獲取對應Connnection,再進行聯合Statement.execute進行操作。 > Connection -> Transaction -> Executor -> StatementHandler ### c.補充:初始化過程與執行過程的關聯 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210218165034775.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) ## 3.Mybatis-Spring 這裡不再詳細展開Mybatis在SpringFramework整合時的配置。 主要針對Mybatis-Spring核心類的剖析,以及Mybatis在SpringBoot中的拆箱即用。 ### a.SqlSessionFactoryBean 在前面的[Mybatis生命週期-初始化過程]()中,提到:SqlSessionFactoryBuilder會通過XMLConfigBuilder 等物件讀取mybatis-config.xml配置檔案以及對映配置資訊,進而得到Configuration 物件,然後建立SqlSessionFactory 物件。 而在Mybatis-Spring中,SqlSessionFactoryBean取代了SqlSessionFactoryBuilder,進行SqlSessionFactory物件的生成。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210218114545358.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2N1cmVraW5n,size_16,color_FFFFFF,t_70#pic_center) 這裡對上述相關類,進行闡述: * SqlSessionFactoryBean:Mybatis-Spring整合中,Spring初始化Mybatis的核心 * FactoryBean<>:宣告該類為一個工廠類 * InitializingBean:利用Spring的生命週期介面,進行Mybatis對應Bean注入時間的時機控制(在配置注入完畢後)。詳見[Initialization Callbacks](https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/spring-framework-reference/core.html#beans-factory-lifecycle-initializingbean) * ApplicationListener<>:通過Spring下ApplicationContext的擴充套件能力,確保在上下文發生變化時,進行Mybatis配置的更新(主要針對Statement)。詳見[Additional Capabilities of the ApplicationContext ](https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/spring-framework-reference/core.html#context-introduction) * SqlSessionFactoryBuilder:SqlSessionFactoryBean通過組合&委託的方式,呼叫SqlSessionFactoryBuilder,從而構建SqlSessionFactory。 ```java public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener { private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); private Resource configLocation; private Configuration configuration; private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); //EnvironmentAware requires spring 3.1 private String environment = SqlSessionFactoryBean.class.getSimpleName(); // 是否採取快速失敗,用於在上下文重新整理時,進行statement重新整理 private boolean failFast; // Mybatis外掛 private Interceptor[] plugins; // 其他Mybatis-Configuration的欄位(略) // 各欄位的getter/setter方法(略) /** * 核心方法,由Spring的生命週期進行控制(鉤子函式-配置設定後,進行觸發) * 進行資料校驗,然後呼叫構建方法-buildSqlSessionFactory() */ @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } /** * */ protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; // 各條件分支,對configuration進行配置的載入,以及不同級別日誌的輸出 // 略 // 委託給SqlSessionFactoryBuilder,進行實際的SqlSessionFactory的構建。後續流程就和Mybatis生命週期-初始化流程一致。詳見前文Mybatis生命週期-初始化流程 return this.sqlSessionFactoryBuilder.build(configuration); } /** * {@inheritDoc} */ @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; } @Override public Class getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); } @Override public boolean isSingleton() { return true; } @Override public void onApplicationEvent(ApplicationEvent event) { if (failFast && event instanceof ContextRefreshedEvent) { // fail-fast -> check all statements are completed this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } } ``` ### b.SpringManagedTransaction 有關Spring下Mybatis的事務,已經在Mybatis的事務模組,說明了。這裡不再贅述。 提醒一下,只有在配置中未指定Mybatis事務管理,Spring才會採用預設事務管理-SpringManagedTransaction。 ### c.SqlSessionTemplate SqlSessionTemplate實現了SqlSession介面,代替Mybatis原有的DefaultSqlSession,完成指定的資料庫操作。 ```java private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } @Override public T selectOne(String statement) { return this.sqlSessionProxy. selectOne(statement); } // 其他略 ``` 其通過委託的方式,呼叫其內部SqlSession sqlSessionProxy,從而完成對應功能。而此處的sqlSessionProxy,最終也是通過DefaultSqlSession實現,除非自定義實現SqlSessionFactory&SqlSession。 ### d.Mybatis-Starter 這部分涉及SpringBoot的自動注入,從而達到拆箱即用的效果。 首先,Spring根據配置,確定並注入DataSource等Bean。 然後,SpringBoot通過spring.factory,確定Mybatis的自動注入類MybatisAutoConfiguration。 最後,根據MybatisAutoConfiguration的@Bean方法,以及已有的配置Bean,進行Mybatis下SqlSessionFactory&SqlSessionTemplate的Bean注入。 #### spring.factory ```java # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration ``` #### MybatisAutoConfiguration ```java @org.springframework.context.annotation.Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnBean(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); private final MybatisProperties properties; private final Interceptor[] interceptors; private final ResourceLoader resourceLoader; private final DatabaseIdProvider databaseIdProvider; private final List configurationCustomizers; public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, ObjectProvider> configurationCustomizersProvider) { this.properties = properties; this.interceptors = interceptorsProvider.getIfAvailable(); this.resou