Java資料持久層

血夜之末發表於2021-03-09

一、前言

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開發者:呼叫統一介面

在這裡插入圖片描述

2.整體結構

在這裡插入圖片描述

對應元件:

  • DriverManager:資料庫驅動管理器
  • Driver:資料庫驅動的抽象介面,用於與資料庫服務進行通訊
  • Connection:與資料庫的連線
  • Statement:用於提交SQL語句
    • Statement:通用介面,繼承自Wrapper。普通的不帶參的查詢SQL;支援批量更新,批量刪除;
    • PreparedStatement:預編譯介面,繼承自Statement。可變引數的SQL,編譯一次,執行多次,效率高; 安全性好,有效防止Sql注入等問題;
    • CallableStatement:繼承自PreparedStatement。支援呼叫儲存過程,提供了對輸出和輸入/輸出引數(INOUT)的支援;
  • ResultSet:用於儲存資料庫結果
  • SQLException:資料庫異常

3.生命週期

a.初始化過程

驅動註冊&配置注入

b.執行過程

在這裡插入圖片描述

4.程式碼示例

原生JDBC較為原始,架構上的設計也是非常薄的。
所以,說得太多,還不如看看應用程式碼。


        // 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.整體框架

在這裡插入圖片描述

對應模組:

  • 介面層
    • 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 語言之於資料庫。

在這裡插入圖片描述

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 中快取了反射操作需要使用的類的元資訊。

在這裡插入圖片描述

從上圖中,可以看出

  • 核心類:
    • 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,用於儲存其對應的類元資訊(屬性,欄位等)


public class Reflector {

  // 對應的Class 型別
  private final Class<?> type;
  // 可讀屬性的名稱集合
  private final String[] readablePropertyNames;
  // 可寫屬性的名稱集合
  private final String[] writablePropertyNames;
  // 屬性相應的setter方法,key是屬性名稱,value是Invoker物件
  private final Map<String, Invoker> setMethods = new HashMap<>();
  // 屬性相應的getter方法集合,key是屬性名稱,value是Invoker物件
  private final Map<String, Invoker> getMethods = new HashMap<>();
  // 屬性相應的setter方法的引數值型別,key是屬性名稱,value是setter方法的引數型別
  private final Map<String, Class<?>> setTypes = new HashMap<>();
  // 屬性相應的getter方法的返回位型別,key是屬性名稱,value是getter方法的返回位型別
  private final Map<String, Class<?>> getTypes = new HashMap<>();
  // 預設構造方法
  private Constructor<?> defaultConstructor;
  // 所有屬性名稱的集合,key是屬性名稱的大寫形式,value是屬性名稱
  private Map<String, String> 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 組合使用, 實現了對複雜的屬性表示式的解析,並實現了獲取指定屬性描述資訊的功能。


/**
 * @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即是屬性表示式。如<result property= ”orders[0].items[1].name” column= ”item2” />
	// 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實現。


/**
 * @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);

  <E> void addAll(List<E> element);

}

MetaObject

ObjectWrapper實現的屬性表示式解析功能,是委託給MetaObject實現的。


/**
 * @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 使用型別模組完成上述兩種轉換。

在這裡插入圖片描述

TypeHandler


/**
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

  // 設定引數。在通過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


public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  @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

在這裡插入圖片描述

呼叫方舉例:org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement

DataSourceFactory


public interface DataSourceFactory {
    void setProperties(Properties var1);

    DataSource getDataSource();
}

UnpooledDataSourceFactory


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


public class UnpooledDataSource implements DataSource {

  // 進行驅動載入的classLoader,可參照JDBC相關處理
  private ClassLoader driverClassLoader;
  // 驅動配置
  private Properties driverProperties;
  // 驅動登錄檔(全量)
  private static Map<String, Driver> 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<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
  }
  
  // 方法略
  
}

e.事務管理模組

在這裡插入圖片描述

TransactionFactory


public interface TransactionFactory {

  default void setProperties(Properties props) {
    // NOP
  }

  Transaction newTransaction(Connection conn);

  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

Transaction


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


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<Obj,Obj>實現。
    • 二級快取:預設關閉。屬於全域性級別的快取。利用CacheExecute -> TransactionalCacheManager -> HashMap<Cache, TransactionalCache> -> TransactionalCache

快取實現,採用裝飾器模式

在這裡插入圖片描述

在這裡插入圖片描述

Cache


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


public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> 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


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


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<Object> 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,資料插入是這樣的:

	sqlSession.insert("insert", userDO);

或者,抽象一下:


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);  
    }  
}

兩個實現,都涉及一個問題,需要手寫


 insert("insert", userDO);  

那麼寫錯,也是完全可能的嘛。但iBatis這部分,與Mybatis一樣,是通過執行時的反射實現的。那麼就無法快速失敗,從而在啟動時檢索出問題。
如果一個不常用的方法實現的入參方法名寫錯了。Boom,線上故障+緊急釋出。

所以,這裡需要一個解決方案,可以在啟動時,就檢索出對應錯誤。
Mybatis給出的答案是,不再需要寫上述實現。Mybatis直接通過Binding模組,直接關聯DAO&對應Mapper。如果對映存在問題,則在啟動時丟擲相應問題。
舉個例子,如果在DAO的入參中沒有String shopCode,而對應Mapper有對應入參注入,則會在啟動時報錯,提示“無法找到對應入參”。

在這裡插入圖片描述

MapperRegistry


public class MapperRegistry {

  // Mybatis全域性Configuration,通過構造器注入
  private final Configuration config;
  // mapperInterface與相應MapperProxyFactory的對映表
  // 如果是sqlSession.xxx的使用,則不經過這裡。因為sqlSession在執行過程中,屬於更底層的位置。詳見後文:生命週期-執行過程
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  // 通過mapperInterface,獲取對應的MapperProxy(type為介面型別)
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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 <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }

  // 初始化過程中,用於新增mapperInterface。詳見下述生命週期-初始化
  public <T> void addMapper(Class<T> 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


public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  // 該介面中,method與對應Invoker的對映表。
  // MapperMethodInvoker與MapperMethod關係,詳見org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy


public class MapperProxy<T> implements InvocationHandler, Serializable {

  // 核心欄位
  // 關聯的SqlSession
  private final SqlSession sqlSession;
  // 當前Mapper,所對應的mapperInterface
  private final Class<T> mapperInterface;
  // 當前Mapper中,Method與Invoker對應的對映表,作為快取。此是由MapperProxyFactory給出
  private final Map<Method, MapperMethodInvoker> methodCache;
  
  // 核心方法
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> 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 中並不記錄任何狀態相關的資訊,所以可以在多個代理物件之間共享


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<paramterName, paramterValue>
        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初始化
在這裡插入圖片描述

Mybatis初始化-解析Mapper
在這裡插入圖片描述

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

在這裡插入圖片描述
從上圖,就可以看出MapperBuilderAssistant這個類的實際地位了。
BaseBuilder作為一個抽象類,提供了一個構建規約。
MapperBuilderAssistant則是實際提供構建能力的assistant。
而XMLStatementBuilder、XMLMapperBuilder等構建器,都是通過組合的方式,將通用能力,委託於MapperBuilderAssistant。

這個部分,這裡只是點一下。

b.執行過程

在這裡插入圖片描述

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.補充:初始化過程與執行過程的關聯

在這裡插入圖片描述

3.Mybatis-Spring

這裡不再詳細展開Mybatis在SpringFramework整合時的配置。
主要針對Mybatis-Spring核心類的剖析,以及Mybatis在SpringBoot中的拆箱即用。

a.SqlSessionFactoryBean

在前面的Mybatis生命週期-初始化過程中,提到:SqlSessionFactoryBuilder會通過XMLConfigBuilder
等物件讀取mybatis-config.xml配置檔案以及對映配置資訊,進而得到Configuration 物件,然後建立SqlSessionFactory 物件。
而在Mybatis-Spring中,SqlSessionFactoryBean取代了SqlSessionFactoryBuilder,進行SqlSessionFactory物件的生成。

在這裡插入圖片描述

這裡對上述相關類,進行闡述:

  • SqlSessionFactoryBean:Mybatis-Spring整合中,Spring初始化Mybatis的核心
  • FactoryBean<>:宣告該類為一個工廠類
  • InitializingBean:利用Spring的生命週期介面,進行Mybatis對應Bean注入時間的時機控制(在配置注入完畢後)。詳見Initialization Callbacks
  • ApplicationListener<>:通過Spring下ApplicationContext的擴充套件能力,確保在上下文發生變化時,進行Mybatis配置的更新(主要針對Statement)。詳見Additional Capabilities of the ApplicationContext
  • SqlSessionFactoryBuilder:SqlSessionFactoryBean通過組合&委託的方式,呼叫SqlSessionFactoryBuilder,從而構建SqlSessionFactory。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  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<? extends SqlSessionFactory> 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,完成指定的資料庫操作。


  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> T selectOne(String statement) {
    return this.sqlSessionProxy.<T> 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


# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

MybatisAutoConfiguration


@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<ConfigurationCustomizer> configurationCustomizers;

  public MybatisAutoConfiguration(MybatisProperties properties,
                                  ObjectProvider<Interceptor[]> interceptorsProvider,
                                  ResourceLoader resourceLoader,
                                  ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                  ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  }

  @PostConstruct
  public void checkConfigFileExists() {
    if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
      Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
      Assert.state(resource.exists(), "Cannot find config location: " + resource
          + " (please add config file or check your Mybatis configuration)");
    }
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }
  
  // 其他方法

}

4.總結

a.Configuration

以Mybatis的Configuration的核心欄位,進行總結

/**
 * @author Clinton Begin
 */
public class Configuration {
  // 核心:環境(特指資料來源環境,同一個Mybatis中,可以存在多個環境)
  // 來源:如mybatis-config.xml中<environments>下,多個<environment>節點資訊
  // 去向:DefaultSqlSessionFactory中提供資料來源(DataSource)、AbstractBaseExecutor中提供資料來源區分標識等
  protected Environment environment;

  // Mybatis的開關配置
  // 如cacheEnabled決定是否採用一級快取(詳見MybatisConfiguration#newExecutor)
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
  protected boolean shrinkWhitespacesInSql;

  // 操作日誌字首
  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  // Mybatis預設執行器型別(SIMPLE, REUSE, BATCH三種型別,詳見newExecutor())
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // Mybatis自動對映行為(NONE、PARTIAL(不對巢狀結果型別進行對映)、FULL三種型別)
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  // Mybatis對未識別對Column&type,進行對對映行為
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  // 配置中變數列表。如mybatis-config.xml中<properties>節點資訊
  // 來源:XMLConfigBuilder.propertiesElement()解析獲取
  // 去向:作為Mybatis的配置項,如username、password等
  protected Properties variables = new Properties();
  // 預設反射工廠。詳見反射模組部分
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 預設物件構建工廠。詳見反射模組部分
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); 

  protected String databaseId;
  
  protected Class<?> configurationFactory;

  // 核心:Mapper方法的管理容器。詳見Binding模組
  // 來源:XMLConfigBuilder#mapperElement(針對xml配置)、XMLMapperBuilder#bindMapperForNamespace(針對xml配置)=》Configuration#addMapper
  // 去向:SqlSessionManager#getMapper、DefaultSqlSession#getMapper =》Configuration#getMapper
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // Mybatis外掛實現,所依賴的責任鏈(暫不深入)
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 核心:Mybatis型別處理器管理容器。詳見型別模組
  // 來源:TypeHandlerRegistry#TypeHandlerRegistry =》 Configuration(預設型別註冊)、TypeHandlerRegistry#register =》 XMLConfigBuilder#typeHandlerElement(自定義型別)
  // 去向:DefaultParameterHandler#setParameters(形參注入)、DefaultResultSetHandler#prepareSimpleKeyParameter(返回結果的型別處理)
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  // Mybatis別名的管理容器
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  // 核心:唯一標識與對應MappedStatement的對映
  // 來源:MapperAnnotationBuilder#handleSelectKeyAnnotation / XMLStatementBuilder#parseStatementNode =》 MapperBuilderAssistant#addMappedStatement =》 Configuration#addMappedStatement
  // 去向:DefaultResultSetHandler#getNestedQueryConstructorValue / DefaultSqlSession#selectList / MapperMethod.SqlCommand#resolveMappedStatement =》 Configuration#getMappedStatement
  // 注:唯一標識:可能包含namespace、字首、自定mapperId(xml)/(typeName+methodName)(註解)
  // 注:MappedStatement與Sql語句,是1:1關係(MappedStatement -> SqlSource -> BoundSql -> String sql)。所以,MappedStatement是針對介面方法的。
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  // 重要:快取對映表,進行名稱空間與其下對應快取的對映
  // 來源:MapperAnnotationBuilder#parseCache / XMLMapperBuilder#cacheElement =》 MapperBuilderAssistant#useNewCache =》 Configuration#addCache
  // 去向:XMLMapperBuilder#parsePendingCacheRefs =》 CacheRefResolver#resolveCacheRef =》 MapperBuilderAssistant#useCacheRef =》 Configuration#getCache(其中有關Incomplete,不用關注)
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  // 核心:唯一標識和對應ResultMap的對映(類似於上面的mappedStatements)
  // 來源&去向:類比上文mappedStatements。
  // 注:mappedStatements關聯Sql語句對映,resultMaps關聯結果集對映
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  // 核心:唯一標識和對應ParameterMap的對映(類似於上面的mappedStatements)
  // 來源:XMLMapperBuilder#parameterMapElement =》 MapperBuilderAssistant#addParameterMap =》 Configuration#addParameterMap
  // 去向:MapperBuilderAssistant#getStatementParameterMap =》 Configuration#getParameterMap
  // 注:mappedStatements關聯Sql語句對映,resultMaps關聯結果集對映,ParameterMap關聯引數集對映
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  // 重要:唯一標識和對應KeyGenerator的對映(類似於上面的mappedStatements)
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  protected final Map<String, String> cacheRefMap = new HashMap<>();

}

四、Mybatis-Plus

1.簡介

a.介紹

b.架構

c.特性:

  • 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
  • 損耗小:啟動即會自動注入基本 CURD,效能基本無損耗,直接物件導向操作
  • 強大的 CRUD 操作:內建通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
  • 支援 Lambda 形式呼叫:通過 Lambda 表示式,方便的編寫各類查詢條件,無需再擔心欄位寫錯
  • 支援主鍵自動生成:支援多達 4 種主鍵策略(內含分散式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
  • 支援 ActiveRecord 模式:支援 ActiveRecord 形式呼叫,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
  • 支援自定義全域性通用操作:支援全域性通用方法注入( Write once, use anywhere )
  • 內建程式碼生成器:採用程式碼或者 Maven 外掛可快速生成 Mapper 、 Model 、 Service 、 Controller 層程式碼,支援模板引擎,更有超多自定義配置等您來使用
  • 內建分頁外掛:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好外掛之後,寫分頁等同於普通 List 查詢
  • 分頁外掛支援多種資料庫:支援 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種資料庫
  • 內建效能分析外掛:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
  • 內建全域性攔截外掛:提供全表 delete 、 update 操作智慧分析阻斷,也可自定義攔截規則,預防誤操作

2.實現

MP的實現,是基於Mybatis的。如果對Mybatis的原始碼有足夠的認識,那麼MP是很容易就入門的。
所以,這裡不會對整個MP進行類似Mybatis的剖析。這裡只針對其核心功能,進行實現的剖析。

a.BaseMapper預設模版

可以說,90%的小夥伴,都是衝著Mybatis-Plus基礎mapper不用寫的特點,入坑的。
那麼Mybatis-Plus,是如何基於Mybatis,實現基礎通用mapper呢?
答案,就是繼承&覆寫,Mybatis的MapperAnnotationBuilder。
Mybatis的MapperAnnotationBuilder,原先是為了提供對@Select等註解的解析。Mybatis-Plus通過繼承&重寫其中的parse方法,實現了Mybatis-Plus自身通用Mapper的注入。

在這裡插入圖片描述
有關MapperRegistry之前的流程,詳見前文對Mybatis初始化流程中,mapper注入的部分。

MybatisMapperAnnotationBuilder


/**
 * 繼承
 * 只重寫了 {@link MapperAnnotationBuilder#parse} 和 #getReturnType
 * 沒有XML配置檔案注入基礎CRUD方法
 * @author Caratacus
 * @since 2017-01-04
 */
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {

    @Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            String mapperName = type.getName();
            assistant.setCurrentNamespace(mapperName);
            parseCache();
            parseCacheRef();
            InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
            for (Method method : type.getMethods()) {
                if (!canHaveStatement(method)) {
                    continue;
                }
                if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                    && method.getAnnotation(ResultMap.class) == null) {
                    parseResultMap(method);
                }
                try {
                    InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
                    SqlParserHelper.initSqlParserInfoCache(mapperName, method);
                    parseStatement(method);
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
                }
            }
            try {
                if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                    parserInjector();
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new InjectorResolver(this));
            }
        }
        parsePendingMethods();
    }
  
  	// 核心方法。進行定製化的通用mapper注入
      void parserInjector() {
        GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
    }
  
  // 其他方法(略)

}

Insert

Mybatis-Plus下BaseMapper的通用Mapper方法,實現都在com.baomidou.mybatisplus.core.injector.methods下。
這裡,就以Insert為例,簡單解析一下。


/**
 * 插入一條資料(選擇欄位插入)
 *
 * @author hubin
 * @since 2018-04-06
 */
@SuppressWarnings("serial")
public class Insert extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
	  // 構建Sql的形參
        String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(null),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
	  // 構建Sql的實參
        String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主鍵處理邏輯,如果不包含主鍵當普通欄位處理
        if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /** 自增主鍵 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
	  // 按照格式要求,配合MethodType,構建對應的sql語句
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
	  // 獲取對應SqlSource
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
	  // 通過AbstractMethod,新增MappedStatement到Mybatis容器-Configuration下Mapper容器:MapperRegistry
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }
}

b.lambda表示式

有關Mybatis-Plus的lambda表示式的實現,涉及的點比較多。這裡給一些建議:首先對函數語言程式設計&流式程式設計有足夠的瞭解,其次需要對wrapper的使用有一定認識,最後剖析Mybatis-Plus中對應部分。

具體實現,詳見:
com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper
com.baomidou.mybatisplus.core.toolkit.LambdaUtils
com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda

com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper
com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper

最後,願與諸君共進步。

五、附錄

補充

  • driver載入:jdbc下的driver載入分為三種方式:
    • driverManager會在第一次呼叫時,會通過loadInitialDrivers()靜態方法,對系統變數中的jdbc.drivers進行驅動註冊
    • 直接呼叫driverManager的register()方法,進行註冊。
    • 具體驅動,在初始化時,會呼叫driverManager的register()方法,進行註冊。例:Class.forName("oracle.jdbc.driver.OracleDriver");
  • vfs:虛擬檔案系統,Mybatis用於載入伺服器相關資源。具體作用,需要繼續檢視。其由一個抽象類VFS與兩個實現類:JBoss6VFS與DefaultVFS,允許自定義實現(Stripes cannot scan classpath correctly with Spring-Boot通過自定義VFS實現,解決springBoot的巢狀jar掃描問題)。
  • SpringBoot預設資料庫連線池:早期採用tomcat的連線池,2.0後改為HikariCP(位於spring-starter-jdbc下)。現在使用SpringBoot2+時,mybatis自動連線SpringBoot的預設資料來源HikariCP。相關注入,詳見:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration。其通過org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.Hikari上的@ConditionalOnMissingBean(DataSource.class)註解,實現優先順序佇列
  • 工廠方法:DataSourceFactory 介面扮演工廠介面的角色。UnpooledDataSourceFactory和PooledDataSourceFactory 則扮演著具體工廠類的角色。而DataSource(Java)介面扮演產品介面的角色。UnpooledDataSource和HikariDataSource都扮演著具體產品類的角色。
  • MyBatis的初始化可以有兩種方式:
    • 基於XML配置檔案:基於XML配置檔案的方式是將MyBatis的所有配置資訊放在XML檔案中,MyBatis通過載入並XML配置檔案,將配置文資訊組裝成內部的Configuration物件
    • 基於Java API:這種方式不使用XML配置檔案,需要MyBatis使用者在Java程式碼中,手動建立Configuration物件,然後將配置引數set 進入Configuration物件中
  • MyBatis和資料庫的互動有兩種方式:
    • 使用傳統的MyBatis提供的API:如SqlSession.selectOne(String statementId, T parameter);
    • 使用Mapper介面
  • 只有Mapper介面方式,才會經過MapperProxyFactory,MapperProxy,MapperMethod,最終都是呼叫sqlSession.xxx()

參考資料

相關文章