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開發者:調用統一接口

在這裏插入圖片描述

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()

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章