天天看點

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

🐦MyBatis源碼概述及運作原了解析

MyBatis的整體架構分為三層,分别是基礎支援層、核心處理層和接口層

🖌 中文注釋源碼Git位址

🖽架構圖

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

📂源碼結構

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

📁parsing包

🗊parsing包對應基礎支援層中的

解析器子產品

,主要負責解析配置檔案

提示:該子產品中涉及XML檔案的解析方式,感興趣的可以自行了解DOM(Document Object Model)解析方式和SAX(Simple API for XML)解析方式,以及從JDK 6.0版本開始,JDK開始支援的StAX(Streaming API for XML)解析方式。
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

其中

☕️XPathParser

類提供解析mybatis-config.xml配置檔案的方法,

XPathParser

中提供了一系列的eval*()方法用于解析boolean、short、long、int、String、Node等類型的資訊,但是在處理String類型資訊時會調用

☕️PropertyParser

類的 parse()方法

public String evalString(Object root, String expression) {
        String result = (String) evaluate(expression, root, XPathConstants.STRING);
        result = PropertyParser.parse(result, variables);
        return result;
    }
           

PropertyParser.parse()方法中會建立

GenericTokenParser

解析器,并将預設值的處理委托給GenericTokenParser.parse()方法

public static String parse(String string, Properties variables) {
        VariableTokenHandler handler = new VariableTokenHandler(variables);

        //将預設值的處理委托給GenericTokenParser.parse()
        GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
        return parser.parse(string);
    }
           

GenericTokenParser是一個通用的占位符解析器,可以解析配置檔案中的變量。底層使用

TokenHandler

類完成對占位符的解析。

📁reflection包

🗊reflection包對應基礎支援層中反射子產品,主要負責處理類的相關資訊

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

reflection包中對常見的反射操作做了進一步封裝,提供了更加簡潔友善的反射API。其中

Reflector

類是MyBatis中反射子產品的基礎,每個Reflector對象都對應一個類,在Reflector中緩存了反射操作需要使用的類的元資訊。

📃Reflector類

Reflector類中有兩個比較核心的方法即擷取目标類的getter和setter方法,以擷取getter方法為例,

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
  • 其中 getClassMethods() 方法會擷取目前類以及其父類中定義的所有方法的唯一簽名以及相應的Method對象,從該方法傳回的Method數組中查找該類中定義的getter方法,然後判斷是否是以get擷取is開頭(之前的版本好像沒有is,是以會導緻一些Boolean的屬性為null,未證明),最後經過多層處理會記錄到Reflector類的getMethods集合中以供後期使用。
  • Reflector.addFields()方法會處理類中定義的所有字段,并且将處理後的字段資訊添加到setMethods集合、setTypes集合
以下為Reflector類的字段資訊:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

📃ReflectorFactory接口與DefaultReflectorFactory實作類

ReflectorFactory接口主要實作了對Reflector對象的建立和緩存

  • MyBatis隻為該接口提供了DefaultReflectorFactory這一個實作類,DefaultReflectorFactory提供的findForClass()方法實作會為指定的Class建立Reflector對象,并将Reflector對象緩存到DefaultReflectorFactory類的reflectorMap屬性中
/**
     * 為指定的Class建立Reflector對象,并将其加入reflectorMap中
     * @param type 指定Class
     * @return Reflector對象
     */
    @Override
    public Reflector findForClass(Class<?> type) {
        if (classCacheEnabled) { //檢測是否開啟緩存
            // synchronized (type) removed see issue #461
            Reflector cached = reflectorMap.get(type);
            if (cached == null) {
                cached = new Reflector(type); //建立Reflector對象
                reflectorMap.put(type, cached); //放入reflectorMap
            }
            return cached;
        } else {
            return new Reflector(type); //未開啟緩存,直接傳回Reflector對象
        }
    }
           

📃TypeParameterResolver類

reflection包中的

TypeParameterResolver

類可以解析存在複雜的繼承關系以及泛型定義時的類的字段、方法參數或方法傳回值的類型,感興趣可以自行檢視源碼,此類也是Reflector類的基礎。

MyBatis源代碼中提供了TypeParameterResolverTest這個測試類,其中從更多角度測試了TypeParameterResolver的功能,感興趣可以參考該測試類的實作,可以更全面地了解TypeParameterResolver的功能。

📃ObjectFactory接口與DefaultObjectFactory類

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
  • MyBatis中有很多子產品會使用到ObjectFactory接口,該接口提供了多個create()方法的重載,通過這些create()方法可以建立指定類型的對象。
  • DefaultObjectFactory類是MyBatis提供的ObjectFactory接口的唯一實作,他是一個反射工廠,其實作ObjectFactory的create()方法是通過調用自身私有的instantiateClass()方法實作的
  • 使用者也可以自定義ObjectFactory實作類,在配置檔案中指定即可
private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      //通過無參構造函數建立對象
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        return constructor.newInstance();
      }
      //根據指定的參數清單查找構造函數,并執行個體化對象
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) {
      StringBuilder argTypes = new StringBuilder();
      if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
        for (Class<?> argType : constructorArgTypes) {
          argTypes.append(argType.getSimpleName());
          argTypes.append(",");
        }
        argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
      }
      StringBuilder argValues = new StringBuilder();
      if (constructorArgs != null && !constructorArgs.isEmpty()) {
        for (Object argValue : constructorArgs) {
          argValues.append(String.valueOf(argValue));
          argValues.append(",");
        }
        argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
      }
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }
           

✒Property工具集

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
📃PropertyTokenizer類
📜示例:

在使用MyBatis的過程中,我們經常會碰到一些屬性表達式,例如,在查詢某使用者(User)的訂單(Order)的結果集如下:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

對應的對象模型如下:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

假設現在需要将結果集中的item1列與使用者第一個訂單(Order)的第一條目(Item)的名稱映射,item2列與使用者第一個訂單(Order)的第二條目(Item)的名稱映射(這裡僅僅是一個示例,在實際生産中很少這樣設計),我們可以得到下面的映射規則:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-iSichaek-1675960016481)(E:\Users\yuel\Documents\🐦MyBatis源碼解析.assets\image-20230207180307970.png)]

在上例中,“orders[0].items[0].name”這種由“.”和“[]”組成的表達式是由PropertyTokenizer進行解析的

📃PropertyNamer類

提供了下列靜态方法幫助完成方法名到屬性名的轉換,以及多種檢測操作。

具體如下:

//将方法名轉為屬性名
    public static String methodToProperty(String name) {
        if (name.startsWith("is")) {
            name = name.substring(2);
        } else if (name.startsWith("get") || name.startsWith("set")) {
            name = name.substring(3);
        } else {
            throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
        }

        if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
        }

        return name;
    }

    //檢測方法名是否對應屬性名
    public static boolean isProperty(String name) {
        return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
    }

    //檢測方法是否為getter方法
    public static boolean isGetter(String name) {
        return name.startsWith("get") || name.startsWith("is");
    }

    //檢測方法是否為setter方法
    public static boolean isSetter(String name) {
        return name.startsWith("set");
    }
           
📃PropertyCopier類

PropertyCopier是一個屬性拷貝的工具類,其核心方法是copyBeanProperties()方法,主要實作相同類型的兩個對象之間的屬性值拷貝。具體如下:

//主要實作相同類型的兩個對象之間的屬性值拷貝
    public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
        Class<?> parent = type;
        while (parent != null) {
            final Field[] fields = parent.getDeclaredFields();//擷取所有屬性
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    //将sourceBean對象的屬性值設定到destinationBean
                    field.set(destinationBean, field.get(sourceBean));
                } catch (Exception e) {
                    // Nothing useful to do, will only fail on final fields, which will be ignored.
                }
            }
            parent = parent.getSuperclass();//繼續拷貝父類中的字段
        }
    }
           

📃MetaClass類

  • MetaClass通過Reflector和PropertyTokenizer組合使用,實作了對複雜的屬性表達式的解析,并實作了擷取指定屬性描述資訊的功能。
  • MetaClass中比較重要的是findProperty()方法,它是通過調用MetaClass.buildProperty()方法實作的,而buildProperty()方法會通過

    PropertyTokenizer類(該類在上文中的屬性工具集中)

    解析複雜的屬性表達式。
  • MetaClass.findProperty()方法隻查找“.”導航的屬性,并沒有檢測下标
    • 以解析User類中的tele.num這個屬性表達式為例解釋上述過程:首先使用PropertyTokenizer解析tele.num表達式得到其children字段為num,name字段為tele;然後将tele追加到builder中儲存,并調用metaClassForProperty()方法為Tele類建立對應的MetaClass對象,調用其buildProperty()方法處理子表達式num,邏輯同上,此時已經沒有待處理的子表達式,最終得到builder中記錄的字元串為tele.num
  • MetaClass中有hasGetter()和hasSetter()兩個方法負責判斷屬性表達式所表示的屬性是否有對應的屬性,本質上是底層調用了Reflector中的方法集合判斷(補充:按照JavaBean規範,有getter和setter方法的字段成為“屬性”,否則稱為“字段”,特殊情況:存在getA()和setA(String)兩個方法,無論類中是否有a字段,我們都應該認為其有a屬性)
📜以hasGetter()為例:
public boolean hasSetter(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);//解析表達式
        if (prop.hasNext()) {//存在處理的子表達式
            //指定屬性有setter方法才能處理子表達式
            if (reflector.hasSetter(prop.getName())) {
                MetaClass metaProp = metaClassForProperty(prop.getName());
                return metaProp.hasSetter(prop.getChildren());
            } else {
                return false;
            }
        } else {
            return reflector.hasSetter(prop.getName());
        }
    }
           

這裡依然通過一個示例分析MetaClass.hasGetter()方法的執行流程。假設現在通過orders[0].id這個屬性表達式,檢測User類中orders字段中的第一個元素(Order對象)的id字段是否有getter方法,大緻步驟如下:

(1)我們調用MetaClass.forClass()方法建立User對應的MetaClass對象并調用其hasGetter()方法開始解析,經過PropertyTokenizer對屬性表達式的解析後,PropertyTokenizer對象的name值為orders,indexName為orders[0],index為0,children為name。

(2)進入到MetaClass.getGetterType()方法,此時(1)處條件成立,調用getGenericGetterType()方法解析orders字段的類型,得到returnType為List<Order>對應的ParameterizedType對象,此時條件(2)成立,更新returnType為Order對應的Class對象。

(3)繼續檢測Order中的id字段是否有getter方法,具體邏輯同上。

另外,MetaClass中有一個public修飾的getGetterType(String)重載,其邏輯與hasGetter()類似,也是先對表達式進行解析,然後調用metaClassForProperty()方法或getGetterType (PropertyTokenizer)方法進行下一步處理

📃ObjectWrapper接口與ObjectWrapperFactory接口

  • ObjectWrapper接口是對對象的包裝,抽象了對象的屬性資訊,它定義了一系列查詢對象屬性資訊的方法,以及更新屬性的方法
  • ObjectWrapperFactory負責建立ObjectWrapper對象
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
💡DefaultObjectWrapperFactory
  • DefaultObjectWrapperFactory實作了ObjectWrapperFactory接口,但它實作的getWrapperFor()方法始終抛出異常,hasWrapperFor()方法始終傳回false,是以該實作實際上是不可用的,但是與ObjectFactory類似,我們可以在mybatis-config.xml中配置自定義的ObjectWrapperFactory實作類進行擴充。
💡BaseWrapper抽象類與BaseWrapper類
  • BaseWrapper是一個實作了ObjectWrapper接口的抽象類,其中封裝了MetaObject對象,并提供了三個常用的方法供其子類使用
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
  • BaseWrapper.resolveCollection()方法會調用MetaObject.getValue()方法,它會解析屬性表達式并擷取指定的屬性
  • BaseWrapper.getCollectionValue()方法和setCollectionValue()方法會解析屬性表達式的索引資訊,然後擷取/設定對應項
  • BeanWrapper類繼承了BaseWrapper抽象類,其中封裝了一個JavaBean對象以及該JavaBean類相應的MetaClass對象,當然,還有從BaseWrapper繼承下來的、該JavaBean對象相應的MetaObject對象
  • BeanWrapper.get()方法和set()方法會根據指定的屬性表達式,擷取/設定相應的屬性值

📃MetaObject

ObjectWrapper提供了擷取/設定對象中指定的屬性值、檢測getter/setter等常用功能,但是ObjectWrapper隻是這些功能的最後一站,我們省略了對屬性表達式解析過程的介紹,而該解析過程是在MetaObject中實作的

MetaObject和ObjectWrapper中關于類級别的方法,例如hasGetter()、hasSetter()、findProperty()等方法,都是直接調用MetaClass的對應方法實作的

其他方法都是關于對象級别的方法,這些方法都是與ObjectWrapper配合實作

📁type包

🗊type包對應基礎支援層中的

類型轉換

子產品

📂包結構:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

type包

主要負責Java類型與JDBC類型轉換,JDBC資料類型與Java語言中的資料類型并不是完全對應的,是以在PreparedStatement為SQL語句綁定參數時,需要從Java類型轉換成JDBC類型,而從結果集中擷取資料時,則需要從JDBC類型轉換成Java類型

其中有三個比較重要的類,分别是:

🖊TypeHandler接口

MyBatis中所有的類型轉換器都繼承了TypeHandler接口,在TypeHandler接口中定義了如下四個方法,這四個方法分為兩類:setParameter()方法負責将資料由JdbcType類型轉換成Java類型;getResult()方法及其重載負責将資料由Java類型轉換成JdbcType類型,最後的實作其實就是調用了PreparedStatement和ResultSet對應類型的get()和set()方法。

🖊TypeHandlerRegistry類

TypeHandlerRegistry類負責管理衆多TypeHandler接口的實作,以及确定何時哪個類型使用哪個TypeHandler

🖊TypeAliasRegistry類

  • 在編寫SQL語句時,使用别名可以友善了解以及維護,例如表名或列名很長時,我們一般會為其設計易懂易維護的别名。MyBatis将SQL語句中别名的概念進行了延伸和擴充,MyBatis可以為一個類添加一個别名,之後就可以通過别名引用該類。
  • MyBatis通過TypeAliasRegistry類完成别名注冊和管理的功能,TypeAliasRegistry的結構比較簡單,它通過TYPE_ALIASES字段(Map<String, Class<?>>類型)管理别名與Java類型之間的對應關系,通過TypeAliasRegistry.registerAlias()方法完成注冊别名。

📁logging包

🗊logging包對應基礎支援層中的

日志子產品

📂包結構:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
  • 在Java開發中常用的日志架構有Log4j、Log4j2、Apache Commons Log、java.util.logging、slf4j等,這些工具對外的接口不盡相同。為了統一這些工具的接口,MyBatis定義了一套統一的日志接口供上層使用,并為上述常用的日志架構提供了相應的擴充卡
  • 在LogFactory類加載時會執行其靜态代碼塊,其邏輯是按序加載并執行個體化對應日志元件的擴充卡,然後使用LogFactory.logConstructor這個靜态字段,記錄目前使用的第三方日志元件的擴充卡
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

📁io包

🗊io包對應基礎支援層中的

資源加載

子產品

🔭包結構:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

🧬ClassLoaderWrapper類

  • 在IO包中提供的ClassLoaderWrapper是一個ClassLoader的包裝器,其中包含了多個ClassLoader對象。通過調整多個類加載器的使用順序,ClassLoaderWrapper可以確定傳回給系統使用的是正确的類加載器。使用ClassLoaderWrapper就如同使用一個ClassLoader對象,ClassLoaderWrapper會按照指定的順序依次檢測其中封裝的ClassLoader對象,并從中選取第一個可用的ClassLoader完成相關功能
  • ClassLoaderWrapper的主要功能可以分為三類,分别是getResourceAsURL()方法、classForName()方法、getResourceAsStream()方法,這三個方法都有多個重載,這三類方法最終都會調用參數為String和ClassLoader[]的重載

🧬Resources類

Resources是一個提供了多個靜态方法的工具類,其中封裝了一個ClassLoaderWrapper類型的靜态字段,Resources提供的這些靜态工具都是通過調用該ClassLoaderWrapper對象的相應方法實作的

🧬ResolverUtil類

  • ResolverUtil可以根據指定的條件查找指定包下的類,其中使用的條件由Test接口表示。ResolverUtil中使用classLoader字段(ClassLoader類型)記錄了目前使用的類加載器,預設情況下,使用的是目前線程上下文綁定的ClassLoader,我們可以通過setClassLoader()方法修改使用類加載器
  • MyBatis提供了兩個常用的Test接口實作,分别是IsA和AnnotatedWith。IsA用于檢測類是否繼承了指定的類或接口,AnnotatedWith用于檢測類是否添加了指定的注解。該接口也可以自定義實作擴充

🧬VFS抽象類

VFS表示虛拟檔案系統(Virtual File System),它用來查找指定路徑下的資源。VFS是一個抽象類,MyBatis中提供了JBoss6VFS 和 DefaultVFS兩個VFS的實作。也可以提供自定義的VFS實作類。初始化時會使用到該類及子類。

📁datasource包

🗊datasource包對應基礎支援層中的

資料源

子產品

🔭包結構:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

🧿PooledDataSource和UnpooledDataSource類

  • 在資料持久層中,資料源是一個非常重要的元件,其性能直接關系到整個資料持久層的性能。在實踐中比較常見的第三方資料源元件有Apache Common DBCP、C3P0、Proxool等,MyBatis不僅可以內建第三方資料源元件,還提供了自己的資料源實作。
  • 常見的資料源元件都實作了javax.sql.DataSource接口,MyBatis自身實作的資料源實作也不例外。MyBatis提供了兩個javax.sql.DataSource接口實作,分别是PooledDataSource和UnpooledDataSource。Mybatis使用不同的DataSourceFactory接口實作建立不同類型的DataSource,這裡使用了工廠方法模式。

🧿DataSourceFactory類以及其具體工廠類

DataSourceFactory接口扮演工廠接口的角色。UnpooledDataSourceFactory 和PooledDataSourceFactory則扮演着具體工廠類的角色,DataSourceFactory如下:

public interface DataSourceFactory {

  //設定DataSource的相關屬性,一般緊跟在初始化完成之後
  void setProperties(Properties props);

  //擷取DataSource對象
  DataSource getDataSource();

}
           
  • 在UnpooledDataSourceFactory的構造函數中會直接建立UnpooledDataSource對象,并初始化UnpooledDataSourceFactory.dataSource字段。UnpooledDataSourceFactory.setProperties()方法會完成對UnpooledDataSource對象的配置
  • UnpooledDataSourceFactory.getDataSource()方法實作比較簡單,它直接傳回dataSource字段記錄的UnpooledDataSource對象。
  • PooledDataSourceFactory繼承了UnpooledDataSourceFactory,但并沒有覆寫setProperties()方法和getDataSource()方法。兩者唯一的差別是PooledDataSourceFactory的構造函數會将其dataSource字段初始化為PooledDataSource對象
  • JndiDataSourceFactory是依賴JNDI服務從容器中擷取使用者配置的DataSource

🧿UnpooledDataSource類

UnpooledDataSource實作了javax.sql.DataSource接口中定義的getConnection()方法及其重載方法,用于擷取資料庫連接配接。每次通過UnpooledDataSource.getConnection()方法擷取資料庫連接配接時都會建立一個新連接配接

🧿PooledDataSource類

資料庫連接配接的建立過程是非常耗時的,資料庫能夠建立的連接配接數也非常有限,是以在絕大多數系統中,資料庫連接配接是非常珍貴的資源,使用資料庫連接配接池就顯得尤為必要。使用資料庫連接配接池會帶來很多好處,例如,可以實作資料庫連接配接的重用、提高響應速度、防止資料庫連接配接過多造成資料庫假死、避免資料庫連接配接洩露等。

資料庫連接配接池在初始化時,一般會建立一定數量的資料庫連接配接并添加到連接配接池中備用。當程式需要使用資料庫連接配接時,從池中請求連接配接;當程式不再使用該連接配接時,會将其傳回到池中緩存,等待下次使用,而不是直接關閉。當然,資料庫連接配接池會控制連接配接總數的上限以及空閑連接配接數的上限,如果連接配接池建立的總連接配接數已達到上限,且都已被占用,則後續請求連接配接的線程會進入阻塞隊列等待,直到有線程釋放出可用的連接配接。如果連接配接池中空閑連接配接數較多,達到其上限,則後續傳回的空閑連接配接不會放入池中,而是直接關閉,這樣可以減少系統維護多餘資料庫連接配接的開銷。

如果将總連接配接數的上限設定得過大,可能因連接配接數過多而導緻資料庫僵死,系統整體性能下降;如果總連接配接數上限過小,則無法完全發揮資料庫的性能,浪費資料庫資源。如果将空閑連接配接的上限設定得過大,則會浪費系統資源來維護這些空閑連接配接;如果空閑連接配接上限過小,當出現瞬間的峰值請求時,系統的快速響應能力就比較弱。是以在設定資料庫連接配接池的這兩個值時,需要進行性能測試、權衡以及一些經驗。

PooledDataSource實作了簡易資料庫連接配接池的功能,其中需要注意的是,PooledDataSource建立新資料庫連接配接的功能是依賴其中封裝的UnpooledDataSource對象實作的。它依賴的元件如下圖所示

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

PooledDataSource并不會直接管理java.sql.Connection對象,而是管理PooledConnection對象。在PooledConnection中封裝了真正的資料庫連接配接對象(java.sql.Connection)以及其代理對象,這裡的代理對象是通過JDK動态代理産生的。PooledConnection繼承了InvocationHandler接口(補充:InvocationHandler接口是jdk動态代理的核心接口)

⚗PooledConnection中的核心字段如下:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

這裡重點關注PooledConnection.invoke()方法的實作,該方法是proxyConnection這個連接配接代理對象的真正代理邏輯,它會對close()方法的調用進行代理,并且在調用真正資料庫連接配接的方法之前進行檢測,代碼如下:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

🧿PoolState類

PoolState是用于管理PooledConnection對象狀态的元件,它通過兩個ArrayList <PooledConnection>集合分别管理空閑狀态的連接配接和活躍狀态的連接配接,定義如下:

//空閑的PooledConnection集合
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();

//活躍的PooledConnection集合
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
           

PooledDataSource中管理的真正的資料庫連接配接對象是由PooledDataSource中封裝的UnpooledDataSource對象建立的,并由PoolState管理所有連接配接的狀态。

PooledDataSource.getConnection()方法首先會調用PooledDataSource.popConnection()方法擷取PooledConnection對象,然後通過PooledConnection.getProxyConnection()方法擷取資料庫連接配接的代理對象。popConnection()方法是PooledDataSource的核心邏輯之一,其具體邏輯如下圖:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

當調用連接配接的代理對象的close()方法時,并未關閉真正的資料連接配接,而是調用PooledDataSource.pushConnection()方法将PooledConnection對象歸還給連接配接池,供之後重用。PooledDataSource.pushConnection()方法也是PooledDataSource的核心邏輯之一,其邏輯如下圖所示

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

PooledDataSource.forceCloseAll()方法,當修改PooledDataSource的字段時,例如資料庫URL、使用者名、密碼、autoCommit配置等,都會調用forceCloseAll()方法将所有資料庫連接配接關閉,同時也會将所有相應的PooledConnection對象都設定為無效,清空activeConnections集合和idleConnections集合。應用系統之後通過PooledDataSource. getConnection()擷取連接配接時,會按照新的配置重新建立新的資料庫連接配接以及相應的PooledConnection對象,如下,在修改屬性都會調用該方法。

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

📁transaction包

🗊transaction包對應基礎支援層中的

事務管理

子產品

🧷包結構:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

🫧Transaction接口

MyBatis使用Transaction接口對資料庫事務進行了抽象,Transaction接口的定義如下:

public interface Transaction {

  /**
   * 擷取對應的資料庫連接配接對象
   */
  Connection getConnection() throws SQLException;

  /**
   * 送出事務
   */
  void commit() throws SQLException;

  /**
   * 復原事務
   */
  void rollback() throws SQLException;

  /**
   * 關閉資料庫連接配接
   */
  void close() throws SQLException;

  /**
   * 擷取事務逾時時間
   */
  Integer getTimeout() throws SQLException;
  
}
           

🫧JdbcTransaction與ManagedTransaction類

Transaction接口有JdbcTransaction、ManagedTransaction兩個實作,其對象分别由JdbcTransactionFactory和ManagedTransactionFactory負責建立。這裡也使用了工廠方法模式。

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

JdbcTransaction依賴于JDBC Connection控制事務的送出和復原。JdbcTransaction中字段的含義如下:

protected Connection connection;//事務對應的資料庫連接配接
protected DataSource dataSource;//資料庫連接配接對應的DataSource
protected TransactionIsolationLevel level;//事務隔離級别
// MEMO: We are aware of the typo. See #941
protected boolean autoCommmit;//是否自動送出
           
  • 在JdbcTransaction的構造函數中會初始化除connection字段之外的其他三個字段,而connection字段會延遲初始化,它會在調用getConnection()方法時通過dataSource.getConnection()方法初始化,并且同時設定autoCommit和事務隔離級别。JdbcTransaction的commit()方法和rollback()方法都會調用Connection對應方法實作的。
  • ManagedTransaction的實作更加簡單,它同樣依賴其中的dataSource字段擷取連接配接,但其commit()、rollback()方法都是空實作,事務的送出和復原都是依靠容器管理的。ManagedTransaction中通過closeConnection字段的值控制資料庫連接配接的關閉行為。相當于使用ManagedTransaction的commit和rollback功能不會對事務有任何的影響(實際上也是因為他并未實作功能),它什麼都不會做,它将事務管理的權利移交給了容器來實作。
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

在實踐中,MyBatis通常會與Spring內建使用,資料庫的事務是交給Spring進行管理的,進而使用Transaction接口的另一個實作類SpringManagedTransaction。

📁binding包

🗊binding包對應基礎支援層中的

Binding

子產品

📡包結構:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
背景:在iBatis(MyBatis的前身)中,查詢一個Blog對象時需要調用SqlSession.queryForObject (“selectBlog”, blogId)方法。其中,SqlSession.queryForObject()方法會執行指定的SQL語句進行查詢并傳回一個結果對象,第一個參數“selectBlog”指明了具體執行的SQL語句的id,該SQL語句定義在相應的映射配置檔案中。如果我們錯将“selectBlog”寫成了“selectBlog1”,在初始化過程中,MyBatis是無法提示該錯誤的,而在實際調用queryForObject(“selectBlog1”, blogId)方法時才會抛出異常,MyBatis提供了binding子產品用于解決上述問題
💉示例:

我們可以定義一個接口(為友善描述,後面統一稱為“Mapper接口”),該示例中為BlogMapper接口,具體代碼如下所示。注意,這裡的BlogMapper接口并不需要繼承任何其他接口,而且開發人員不需要提供該接口的實作

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

該Mapper接口中定義了SQL語句對應的方法,這些方法在MyBatis初始化過程中會與映射配置檔案中定義的SQL語句相關聯。如果存在無法關聯的SQL語句,在MyBatis的初始化節點就會抛出異常。我們可以調用Mapper接口中的方法執行相應的SQL語句,這樣編譯器就可以幫助我們提早發現上述問題。查詢Blog對象就變成了如下代碼:

//首先,擷取BlogMapper對應的代理對象
	BlogMapper mapper = session.getMapper(BlogMapper.class);

	//調用Mapper接口中定義的方法執行對應的SQL語句
	Blog blog = mapper.selectBlog(1);
           
🪝該子產品中核心元件之間的關系:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

⛏MapperRegistry&MapperProxyFactory

MapperRegistry是Mapper接口及其對應的代理對象工廠的注冊中心。提供給Configuration對象使用,(Configuration是MyBatis全局性的配置對象,在MyBatis初始化的過程中,所有配置資訊會被解析成相應的對象并記錄到Configuration對象中)

MapperRegistry中字段的含義和功能如下:
private final Configuration config;//MyBatis全局唯一的配置對象,其中包含了所有配置資訊

//記錄Mapper接口與對應MapperProxyFactory之間的關系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
           

在MyBatis初始化過程中會讀取映射配置檔案以及Mapper接口中的注解資訊,并調用MapperRegistry.addMapper()方法填充MapperRegistry.knownMappers集合,該集合的key是Mapper接口對應的Class對象,value為MapperProxyFactory工廠對象,可以為Mapper接口建立代理對象。

MapperRegistry.addMapper()方法如下:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

在需要執行某SQL語句時,會先調用MapperRegistry.getMapper()方法擷取實作了Mapper接口的代理對象

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //查找指定type對應的MapperProxyFactory對象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        //建立實作了type的接口代理對象
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
           

(上面所提到的session.getMapper(BlogMapper.class)方法得到的實際上是MyBatis通過JDK動态代理為BlogMapper接口生成的代理對象)

MapperProxyFactory主要負責建立代理對象,其中核心字段的含義和功能如下:

//目前MapperProxyFactory對象可以建立實作了mapperInterface接口的代理對象
private final Class<T> mapperInterface;

//緩存 ,key是mapperInterface接口中某方法對應的Method對象,value是對應的MapperMethod對象
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
           

📱MapperProxy

MapperProxy實作了InvocationHandler接口,是代理對象的核心邏輯,MapperProxy中核心字段的含義和功能如下:

private final SqlSession sqlSession;//記錄關聯的SqlSession對象
private final Class<T> mapperInterface;//Mapper接口對應的Class對象

// 用于緩存MapperMethod對象,其中key是Mapper接口中的方法對應的Method對象,value是對應的MapperMethod對象,MapperMethod對象會完成參數的轉換以及SQL語句的執行功能需要注意的是,MapperMethod中并不記錄如何狀态相關的資訊,是以可能在多個代理對象之間共享
private final Map<Method, MapperMethod> methodCache;
           

MapperProxy.invoke()方法是代理對象執行的主要邏輯,實作如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            //如果目标方法繼承自Object,則直接調用
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) { //針對jdk7
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    //從緩存中擷取MapperMethod對象,如果緩存中沒有,則建立新的MapperMethod對象并添加到緩存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //調用MapperMethod.execute()方法執行sql語句
    return mapperMethod.execute(sqlSession, args);
}
           

MapperProxy.cachedMapperMethod()方法主要負責維護methodCache這個緩存集合,實作如下:

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method); //在緩存中查找MapperMethod
    if (mapperMethod == null) {
        //建立MapperMethod對象,并添加到methodCached中
        mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
        methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
}
           

🔬MapperMethod

MapperMethod中封裝了Mapper接口中對應方法的資訊,以及對應SQL語句的資訊。讀者可以将MapperMethod看作連接配接Mapper接口以及映射配置檔案中定義的SQL語句的橋梁。MapperMethod中各個字段的資訊如下:

private final SqlCommand command;//記錄了SQL語句的名稱和類型
private final MethodSignature method;//Mapper接口中對應方法的相關資訊
           
🪞SqlCommand
  • SqlCommand是MapperMethod中定義的内部類,它使用name字段記錄了SQL語句的名稱,使用type字段(SqlCommandType類型)記錄了SQL語句的類型。SqlCommandType是枚舉類型,有效取值為UNKNOWN、INSERT、UPDATE、DELETE、SELECT、FLUSH。
  • SqlCommand構造函數中會通過

    接口名類全限定名與對應的方法名相加組成的statementId

    在Configuration中查找指定的MappedStatement對象來指派SqlCommand的name和type字段,若是沒有查到MappedStatement對象且目前方法不是辨別@Flush注解的方法,則會爆出

    Invalid bound statement (not found)

    異常。
🪬MethodSignature

MethodSignature也是MapperMethod中定義的内部類,其中封裝了Mapper接口中定義的方法的相關資訊,MethodSignature中核心字段的含義如下:

private final boolean returnsMany;//傳回值類型是否為Collection類型或是數組類型
private final boolean returnsMap;//傳回值是否為Map類型
private final boolean returnsVoid;//傳回值是否為void
private final boolean returnsCursor;//傳回值是否為Cursor類型
private final Class<?> returnType;//傳回值類型
private final String mapKey;//傳回值類型是map,則該字段記錄了作為key的列名
private final Integer resultHandlerIndex;//用來标記該方法參數清單中resultHandler位置
private final Integer rowBoundsIndex;//用來标記該方法參數清單中 rowBounds位置
private final ParamNameResolver paramNameResolver;//該方法對應的ParamNameResolver對象
           

其中ParamNameResolver用來處理Mapper接口中定義的方法的參數清單,ParamNameResolver使用name字段(SortedMap<Integer, String>類型)記錄了參數在參數清單中的位置索引與參數名稱之間的對應關系,其中key表示參數在參數清單中的索引位置,value表示參數名稱(),參數名稱可以通過@Param注解指定,如果沒有指定@Param注解,則使用參數索引作為其名稱。

ParamNameResolver的hasParamAnnotation字段(boolean類型)記錄對應方法的參數清單中是否使用了@Param注解。

在ParamNameResolver的構造方法中,會通過反射的方式讀取Mapper接口中對應方法的資訊,并初始化上述兩個字段

在MethodSignature的構造函數中會解析相應的Method對象,并初始化上述字段。

回到MapperMethod繼續分析,MapperMethod中最核心的方法是execute()方法,它會根據SQL語句的類型調用SqlSession對應的方法完成資料庫操作

  • 當執行INSERT、UPDATE、DELETE類型的SQL語句時,其執行結果都需要經過MapperMethod.rowCountResult()方法處理。SqlSession中的insert()等方法傳回的是int值,rowCountResult()方法會将該int值轉換成Mapper接口中對應方法的傳回值
  • 如果Mapper接口中定義的方法準備使用ResultHandler處理查詢結果集,則通過MapperMethod.executeWithResultHandler()方法處理
  • 如果Mapper接口中對應方法的傳回值為數組或是Collection接口實作類,則通過MapperMethod.executeForMany ()方法處理
  • 如果Mapper接口中對應方法的傳回值為Map類型,則通過MapperMethod.executeForMap ()方法處理
  • executeForCursor()方法與executeForMap ()方法類似,唯一差別就是調用了SqlSession的selectCursor()方法

📁cache包

🗊cache包對應基礎支援層中的

緩存

子產品

📂包結構

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

MyBatis中的緩存是兩層結構的,分為一級緩存、二級緩存,但在本質上是相同的,它們使用的都是Cache接口的實作,此處隻對源碼進行大概解釋。使用功能後續與執行器整合介紹,在緩存子產品中涉及了裝飾器模式的相關知識。是以在此補充一下裝飾器模式。

🧬裝飾器模式的類圖:
MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
  • **Component(元件):**元件接口定義了全部元件實作類以及所有裝飾器實作的行為
  • ConcreteComponent(具體元件實作類):具體元件實作類實作了Component接口。通常情況下,具體元件實作類就是被裝飾器裝飾的原始對象,該類提供了Component接口中定義的最基本的功能,其他進階功能或後續添加的新功能,都是通過裝飾器的方式添加到該類的對象之上的
  • Decorator(裝飾器):所有裝飾器的父類,它是一個實作了Component接口的抽象類,并在其中封裝了一個Component對象,也就是被裝飾的對象。而這個被裝飾的對象隻要是Component類型即可,這就實作了裝飾器的組合和複用。如圖2-41所示,裝飾器C(ConcreteDecorator1類型)修飾了裝飾器B(ConcreteDecorator2類型)并為其添加功能W,而裝飾器B(ConcreteDecorator2類型)又修飾了元件A(ConcreteComponent類型)并為其添加功能V。其中,元件對象A提供的是最基本的功能,裝飾器B和裝飾器C會為元件對象A添加新的功能。
    MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析
  • **ConcreteDecorator:**具體的裝飾器實作類,該實作類要向被裝飾對象添加某些功能。如圖2-41所示,裝飾器B、C就是該角色,被裝飾的對象隻要是Component類型即可

在MyBatis的緩存子產品中,使用了裝飾器模式的變體,其中将Decorator接口和Component接口合并為一個Component接口,類圖如下:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

📆Cache接口及其實作

MyBatis中緩存子產品相關的代碼位于cache包下,其中Cache接口是緩存子產品中最核心的接口,它定義了所有緩存的基本行為,Cache接口的定義如下:

public interface Cache {

    /**
     * 該緩存對象的id
     */
    String getId();

    /**
     * 向緩存中添加資料,一般情況下,key是CacheKey
     */
    void putObject(Object key, Object value);

    /**
     * 根據指定的key,在緩存中查找對應的緩存結果對象
     */
    Object getObject(Object key);

    /**
     * 删除key對應的項
     */
    Object removeObject(Object key);

    /**
     * 清空緩存
     */
    void clear();

    /**
     * 緩存的個數,該方法不會被MyBatis核心代碼使用,是以可以提供空現實
     */
    int getSize();

    /**
     * 擷取讀寫鎖,該方法不會被MyBatis核心代碼使用,是以可以提供空現實
     * @return A ReadWriteLock
     */
    ReadWriteLock getReadWriteLock();

}
           

Cache接口的實作類有多個,但大部分都是裝飾器,隻有PerpetualCache提供了Cache接口的基本實作。

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

⚗PerpetualCache

PerpetualCache在緩存子產品中扮演着ConcreteComponent的角色,其實作比較簡單,底層使用HashMap記錄緩存項,也是通過該HashMap對象的方法實作的Cache接口中定義的相應方法

下面來介紹cache.decorators包下提供的裝飾器,它們都直接實作了Cache接口,扮演着ConcreteDecorator的角色。這些裝飾器會在PerpetualCache的基礎上提供一些額外的功能,通過多個組合後滿足一個特定的需求,在二級緩存中,會見到這些裝飾器是如何完成動态組合的。

🏔BlockingCache

BlockingCache是阻塞版本的緩存裝飾器,它會保證隻有一個線程到資料庫中查找指定key對應的資料假設線程A在BlockingCache中未查找到keyA對應的緩存項時,線程A會擷取keyA對應的鎖,這樣後續線程在查找keyA時會發生阻塞,如圖所示:

MyBatis源碼概述及運作原了解析(篇一)🐦MyBatis源碼概述及運作原了解析

🌋FifoCache&LruCache

  • 在很多場景中,為了控制緩存的大小,系統需要按照一定的規則清理緩存。FifoCache是先入先出版本的裝飾器,當向緩存添加資料時,如果緩存項的個數已經達到上限,則會将緩存中最老(即最早進入緩存)的緩存項删除。
  • LruCache是按照近期最少使用算法(Least Recently Used,LRU)進行緩存清理的裝飾器,在需要清理緩存時,它會清除最近最少使用的緩存項。

🏛SoftCache&WeakCache

在開始介紹SoftCache和WeakCache實作之前,先簡單介紹一下Java提供的4種引用類型,它們分别是強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和幽靈引用(Phantom Reference)

  • 強引用是Java程式設計中最普遍的引用,例如Object obj = new Object()中,建立的Object對象就是被強引用的。如果一個對象被強引用,即使是Java虛拟機記憶體空間不足時,GC(垃圾收集器)也絕不會回收該對象。當Java虛拟機記憶體不足時,就可能會導緻記憶體溢出,我們常見的就是OutOfMemoryError異常。
  • 軟引用是引用強度僅弱于強引用的一種引用,它使用類SoftReference 來表示。當Java虛拟機記憶體不足時,GC會回收那些隻被軟引用指向的對象,進而避免記憶體溢出。在GC釋放了那些隻被軟引用指向的對象之後,虛拟機記憶體依然不足,才會抛出OutOfMemoryError異常。軟引用适合引用那些可以通過其他方式恢複的對象,例如,資料庫緩存中的對象就可以從資料庫中恢複,是以軟引用可以用來實作緩存,下面将要介紹的SoftCache就是通過軟引用實作的
  • 弱引用的強度次于軟引用。弱引用使用WeakReference來表示,它可以引用一個對象,但并不阻止被引用的對象被GC回收。在JVM虛拟機進行垃圾回收時,如果指向一個對象的所有引用都是弱引用,那麼該對象會被回收。
  • 幽靈引用,又叫“虛引用”,它是最弱的一種引用類型,由類PhantomReference表示。在引用的對象未被GC回收時,調用前面介紹的SoftReference以及WeakReference的get()方法,得到的是其引用的對象;當引用的對象已經被GC回收時,則得到null。但是PhantomReference.get()方法始終傳回null。

SoftCache中各個字段的含義如下:

//在SoftCache中,最近使用的一部分緩存不會被GC回收,這就是通過将其value添加到hardLinksToAvoidGarbageCollection集合中實作的(即有強引用指向其value),hardLinksToAvoidGarbageCollection集合是LinkedList類型
private final Deque<Object> hardLinksToAvoidGarbageCollection;

//引用隊列,用于記錄被GC回收的的緩存所對應的SoftEntry對象
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;// 被裝飾的底層Cache對象
private int numberOfHardLinks;//強連接配接的個數  預設值是256
           
  • SoftCache中緩存項的value是SoftEntry對象,SoftEntry繼承了SoftReference,其中指向key的引用是強引用,而指向value的引用是軟引用。
private static class SoftEntry extends SoftReference<Object> {
    private final Object key;

    SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
        super(value, garbageCollectionQueue);//指向value的引用是軟引用
        this.key = key;//強引用
    }
}
           
  • SoftCache.putObject()方法除了向緩存中添加緩存項,還會清除已經被GC回收的緩存項,其具體實作如下:
@Override
public void putObject(Object key, Object value) {
    removeGarbageCollectedItems();//清除已經被gc回收的緩存
    //添加緩存項
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}

private void removeGarbageCollectedItems() {
    SoftEntry sv;
    //周遊queueOfGarbageCollectedEntries集合
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
        delegate.removeObject(sv.key);//将已經被gc回收的value對象對應的緩存項清除
    }
}
           
  • SoftCache.getObject ()方法除了從緩存中查找對應的value,處理被GC回收的value對應的緩存項,還會更新hardLinksToAvoidGarbageCollection集合,具體實作如下:
public Object getObject(Object key) {
    Object result = null;
    //從緩存中查找對應的緩存項
    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
    SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
    if (softReference != null) {//檢測緩存中是否有對應的緩存項
        result = softReference.get();//擷取softReference引用中的value
        if (result == null) {//已經被gc回收
            delegate.removeObject(key);//從緩存中清除對應緩存項
        } else {
            // See #586 (and #335) modifications need more than a read lock
            synchronized (hardLinksToAvoidGarbageCollection) {
                //緩存項的value添加到hardLinksToAvoidGarbageCollection集合中儲存
                hardLinksToAvoidGarbageCollection.addFirst(result);
                if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
                    //超過numberOfHardLinks,則将最老的緩存項清除,此處類似先進先出
                    hardLinksToAvoidGarbageCollection.removeLast();
                }
            }
        }
    }
    return result;
}
           
  • SoftCache.removeObject()方法在清除緩存項之前,也會調用removeGarbageCollectedItems()方法清理被GC回收的緩存項
  • SoftCache.clear()方法首先清理hardLinksToAvoidGarbageCollection集合,然後清理被GC回收的緩存項,最後清理底層delegate緩存中的緩存項

WeakCache的實作與SoftCache基本類似,唯一的差別在于其中使用WeakEntry(繼承自WeakReference)封裝真正的value對象,其他實作完全一樣

🗽ScheduledCache&LoggingCache&Synchronized&CacheSerializedCache

  • ScheduledCache是周期性清理緩存的裝飾器,它的clearInterval字段記錄了兩次緩存清理之間的時間間隔,預設是一小時,lastClear字段記錄了最近一次清理的時間戳。ScheduledCache 的getObject()、putObject()、removeObject()等核心方法在執行時,都會根據這兩個字段檢測是否需要進行清理操作,清理操作會清空緩存中所有緩存項。
  • LoggingCache在Cache的基礎上提供了日志功能,它通過hit字段和request字段記錄了Cache的命中次數和通路次數。在LoggingCache.getObject()方法中會統計命中次數和通路次數這兩個名額,并按照指定的日志輸出方式輸出命中率。LoggingCache代碼比較簡單,請讀者參考代碼學習。
  • -SynchronizedCache通過在每個方法上添加synchronized關鍵字,為Cache添加了同步功能,有點類似于JDK中Collections中的SynchronizedCollection内部類的實作。SynchronizedCache代碼比較簡單,請讀者參考代碼學習。
  • SerializedCache提供了将value對象序列化的功能。SerializedCache在添加緩存項時,會将value對應的Java對象進行序列化,并将序列化後的byte[]數組作為value存入緩存。SerializedCache在擷取緩存項時,會将緩存項中的byte[]數組反序列化成Java對象。使用前面介紹的Cache裝飾器實作進行裝飾之後,每次從緩存中擷取同一key對應的對象時,得到的都是同一對象,任意一個線程修改該對象都會影響到其他線程以及緩存中的對象;而SerializedCache每次從緩存中擷取資料時,都會通過反序列化得到一個全新的對象

⛪CacheKey類

在Cache中唯一确定一個緩存項需要使用緩存項的key,MyBatis中因為涉及動态SQL等多方面因素,其緩存項的key不能僅僅通過一個String表示,是以MyBatis提供了CacheKey類來表示緩存項的key,在一個CacheKey對象中可以封裝多個影響緩存項的因素。

CacheKey中可以添加多個對象,由這些對象共同确定兩個CacheKey對象是否相同。CacheKey中核心字段的含義和功能如下:

private int multiplier;//參與計算hashcode,預設值是37

private int hashcode;//CacheKey對象的hashcode,初始值是17

private long checksum;//校驗和

private List<Object> updateList;//由該集合中的所有對象共同決定兩個CacheKey是否相同

private int count;//updateList集合的個數
           

基礎支援層源碼梳理到此為止。在後續的核心處理層和接口層在使用時,會有更詳細的介紹。⛺

謝謝觀看🌄🌄🌄