主頁 > 軟體設計 > Java資料持久層

Java資料持久層

2021-04-01 21:50:31 軟體設計

一、前言

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 = https://www.cnblogs.com/Tiancheng-Duan/p/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 = https://www.cnblogs.com/Tiancheng-Duan/p/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 = https://www.cnblogs.com/Tiancheng-Duan/p/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 = https://www.cnblogs.com/Tiancheng-Duan/p/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 = https://www.cnblogs.com/Tiancheng-Duan/p/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()

參考資料

  • JDBC指南(W3C版)
  • 老調重彈:JDBC系列 之 <驅動加載原理全面決議>
  • 老調重彈:JDBC系列 之 <JDBC層次結構和基本構成>
  • 《深入理解mybatis原理》 Mybatis初始化機制詳解
  • 面試:你知道MyBatis執行程序之初始化是如何執行的嗎?
  • JDBC層次結構和基本構成
  • 《Mybatis技術內幕》
  • 《深入理解mybatis原理》 MyBatis的架構設計以及實體分析
  • Mybatis資料源
  • Mybatis實作@Select等注解動態組合SQL陳述句
  • Stripes cannot scan classpath correctly with Spring-Boot
  • Github_HikariCP
  • mybatis快取機制
  • SpringFramework-Core
  • Github_Mybatis-3
  • Github_Mybatis-redis-cache
  • Github_Mybatis-Plus

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/270927.html

標籤:架構設計

上一篇:微服務想用好,先把分布式和微服務之間的關系搞清楚

下一篇:你以為在做的是微服務?不!你只是做了個比單體還糟糕的分布式單體!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more