天天看點

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

Mybatis反射子產品的解讀與分析

在寫之前我們先濾清orm查詢的大概過程,然後通過每個子產品去濾清思路

思路 :先了解各個類的結構,然後在檢視相對應的源碼

目錄

  • Mybatis反射子產品的解讀與分析
  • orm架構查詢資料過程
  • 一 反射子產品的大概分析
            • 如果我們的相關類如果沒有get()set()方法的話我們的Mybatis會把他自動生成
            • **Mybatis大概的執行思路:**
  • 二、MyBatis 流程概述
          • 建造者模式的了解與使用Demo
            • 建造者模式示例:
        • 建造者模式在mybatis中的應用
      • 接着說mybatis的流程
        • XMLConfigBuilder的初始化的過程
            • XMl的配置加載過程
      • Mybatis 的接口層
        • SqlSessionFactory
  • 總結

orm架構查詢資料過程

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

提示:以下是本篇文章正文内容,下面案例可供參考

一 反射子產品的大概分析

在分析之前我們先看一下下面這幾個類,以及作用

反射是Mybatis 子產品中類最多的子產品,通過反射實作了 POJO 對象的執行個體化和POJO 的屬性賦

值,相對 JDK 自帶的反射功能,MyBatis 的反射子產品功能更為強大,性能更高;反射子產品關

鍵的幾個類如下:

  • ObjectFactory:MyBatis 每次建立結果對象的新執行個體時,它都會使用對象工廠(ObjectFactory)

    去建構POJO;

  • ReflectorFactory:建立Reflector 的工廠類,Reflector 是MyBatis 反射子產品的基礎,每個Reflector

    對象都對應一個類,在其中緩存了反射操作所需要的類元資訊;

  • ObjectWrapper:對對象的包裝,抽象了對象的屬性資訊,他定義了一系列查詢對象屬性信

    息的方法,以及更新屬性的方法 是對我們的對象進行指派的 ;

    這個裡面最重要的方法就是 set方法 BeanWrapper 裡面的set方法就是用來指派的

    代碼位置

BeanWrapper impl ObjectWarpper方法
org.apache.ibatis.reflection.wrapper.BeanWrapper#set
org.apache.ibatis.reflection.wrapper.BeanWrapper#setBeanProperty 
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
    try {
        //拿到set方法
      Invoker method = metaClass.getSetInvoker(prop.getName());
      Object[] params = {value};
      try {
        method.invoke(object, params);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    } catch (Throwable t) {
      throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
    }
  }
           

源碼分析之反射工具類的執行個體 了解上面說的,每一個是幹什麼的?對應着怎麼使用

//反射工具類初始化
		ObjectFactory objectFactory = new DefaultObjectFactory();
		TUser user = objectFactory.create(TUser.class);
		ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
		ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
		MetaObject metaObject = MetaObject.forObject(user, objectFactory, objectWrapperFactory, reflectorFactory);
//
//		//使用Reflector讀取類元資訊
		Reflector findForClass = reflectorFactory.findForClass(TUser.class);
		Constructor<?> defaultConstructor = findForClass.getDefaultConstructor();
		String[] getablePropertyNames = findForClass.getGetablePropertyNames();
		String[] setablePropertyNames = findForClass.getSetablePropertyNames();
		System.out.println(defaultConstructor.getName());
		System.out.println(Arrays.toString(getablePropertyNames));
		System.out.println(Arrays.toString(setablePropertyNames));
  		//使用ObjectWrapper讀取對象資訊,并對對象屬性進行指派操作
		TUser userTemp = new TUser();
		ObjectWrapper wrapperForUser = new BeanWrapper(metaObject, userTemp);
		String[] getterNames = wrapperForUser.getGetterNames();
		String[] setterNames = wrapperForUser.getSetterNames();
		System.out.println(Arrays.toString(getterNames));
		System.out.println(Arrays.toString(setterNames));

		PropertyTokenizer prop = new PropertyTokenizer("userName");
		wrapperForUser.set(prop, "家哥哥");
		System.out.println(userTemp);
           
  • ObjectWrapperFactory: ObjectWrapper 的工廠類,用于建立 ObjectWrapper ;
  • MetaObject:封裝了對象元資訊,包裝了MyBatis 中五個核心的反射類。也是提供給外部使用的反射工具類,可以利用它可以讀取或者修改對象的屬性資訊;MetaObject 的類結構如

    跟反射相關的類都封裝在了裡面的 :

如果我們的相關類如果沒有get()set()方法的話我們的Mybatis會把他自動生成

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

然後通過這兩個方法來對目前的this進行指派

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

當然我們可以看到,目前的對象中是沒有id這個屬性的 ,

在我們addFields方法中會自動給他指派

debug的位置的

org.apache.ibatis.reflection.Reflector#Reflector  
           

裡面的構造方法,由此我便知道了mybatis的反射功能是幹什麼的,也就是我們知道為什麼mybatis在xml中可以根據配置的傳回資訊而生成的相對應的執行個體資料傳回給我們的資料

Mybatis大概的執行思路:

//模拟資料庫行資料轉化成對象
        ObjectFactory objectFactory = new DefaultObjectFactory();
        TUser user = objectFactory.create(TUser.class);
        ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
        ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
        //生成MateObject的
        MetaObject metaObject = MetaObject.forObject(user, objectFactory, objectWrapperFactory, reflectorFactory);


        //1.模拟從資料庫讀取資料
        Map<String, Object> dbResult = new HashMap<>();
        dbResult.put("id", 1);
        dbResult.put("userName", "lison");
        dbResult.put("realName", "李曉宇");
        TPosition tp = new TPosition();
        tp.setId(1);
        dbResult.put("position_id", tp);
        //2.模拟映射關系
        Map<String, String> mapper = new HashMap<String, String>();
        mapper.put("id", "id");
        mapper.put("userName", "userName");
        mapper.put("realName", "realName");
        mapper.put("position", "position_id");

        //3.使用反射工具類将行資料轉換成pojo

        //擷取BeanWrapper,既包括類中繼資料,同時還能對對象的屬性指派
        BeanWrapper objectWrapper = (BeanWrapper) metaObject.getObjectWrapper();

        Set<Entry<String, String>> entrySet = mapper.entrySet();
        //周遊映射關系
        for (Entry<String, String> colInfo : entrySet) {
            String propName = colInfo.getKey();//獲得pojo的字段名稱
            Object propValue = dbResult.get(colInfo.getValue());//模拟從資料庫中加載資料對應列的值
            PropertyTokenizer proTokenizer = new PropertyTokenizer(propName);
            objectWrapper.set(proTokenizer, propValue);//将資料庫的值指派到pojo的字段中
        }
        System.out.println(metaObject.getOriginalObject());
           

下所示:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

可以打開相關源碼去檢視每一個裡面的注釋位址我會放到github位址

Reflector :裡面儲存了每一個對象的類型,如每個屬性,還有get,set方法等等 ,也就是說我們每執行個體化一個類,mybatis都會生成一個對應的Reflector來存儲這個對象相對應的方法 而在DefaultReflectorFactory 中有一個map是用來緩存每個class相對應的Reflector的

//用于緩存生成的Reflector的中繼資料,避免反射性能較低
  private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
           

如果我們的實體類沒有get set 方法的話它會根據我們的實體類去自動生成get,set方法

我們可以發現mybatis的反射類是非常強大的,我們如果項目中遇到需要反射的地方我們可以拿來即用

二、MyBatis 流程概述

通過對快速入門代碼的分析,可以把MyBatis 的運作流程分為三大階段:

  1. 初始化階段:讀取 XML 配置檔案和注解中的配置資訊,建立配置對象,并完成各個模

    塊的初始化的工作;

  2. 代理封裝階段:封裝 iBatis 的程式設計模型,使用 mapper 接口開發的初始化工作;
  3. 資料通路階段:通過 SqlSession 完成SQL 的解析,參數的映射、SQL 的執行、結果的解析過程;

    快速入門代碼階段劃分圖示:

    Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結
建造者模式的了解與使用Demo

什麼是建造者模式 ?

在配置加載階段大量的使用了建造者模式,首先學習建造者模式。建造者模式Pattern)使用多個簡單的對象一步一步建構成一個複雜的對象。這種類型的設計模式屬于建立型模式,它提供了一種建立對象的最佳方式。建造者模式類圖如下:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

各要素如下:

 Product:要建立的複雜對象

 Builder:給出一個抽象接口,以規範産品對象的各個組成成分的建造。這個接口規定要實作複雜對象的哪些部分的建立,并不涉及具體的對象部件的建立;

 ConcreteBuilder:實作 Builder 接口,針對不同的商業邏輯,具體化複雜對象的各部分的建立。 在建造過程完成後,提供産品的執行個體;

 Director:調用具體建造者來建立複雜對象的各個部分,在指導者中不涉及具體産品的資訊,隻負責保證對象各部分完整建立或按某種順序建立;

關于建造器模式的擴充知識:流式程式設計風格越來越流行,如 zookeeper 的 Curator、JDK8 的流式程式設計等等都是例子。流式程式設計的優點在于代碼程式設計性更高、可讀性更好,缺點在于對程式員編碼要求更高、不太利于調試。建造者模式是實作流式程式設計風格的一種方式;

與工廠模式差別

建造者模式應用場景如下:

 需要生成的對象具有複雜的内部結構,執行個體化對象時要屏蔽掉對象内部的細節,讓上層代碼與複雜對象的執行個體化過程解耦,可以使用建造者模式;簡而言之,如果“遇到多個構造器參數時要考慮用建構器”;

 對象的執行個體化是依賴各個元件的産生以及裝配順序,關注的是一步一步地組裝出目标對象,可以使用建造器模式;

建造者模式與工廠模式的差別在于:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

建造者模式示例:

比如我們有一個發紅包的程式這是

紅包的接口(示例):

public interface RedPacketBuilder {
	
	RedPacketBuilder setPublisherName(String publishName);

	RedPacketBuilder setAcceptName(String acceptName);

	RedPacketBuilder setPacketAmount(BigDecimal packetAmount);

	RedPacketBuilder setPacketType(int packetType);

	RedPacketBuilder setPulishPacketTime(Date pushlishPacketTime);

	RedPacketBuilder setOpenPacketTime(Date openPacketTime);

    RedPacket build();
}
           

接口的實作類以及建造者模式的設計(示例):

public class RedPacketBuilderImpl implements RedPacketBuilder {
	
	private String publisherName;

    private String acceptName;

    private BigDecimal packetAmount;
    
    private int packetType;

    private Date pulishPacketTime;

    private Date openPacketTime;

    public static RedPacketBuilderImpl getBulider(){
        return new RedPacketBuilderImpl();
    }


    @Override
    public RedPacketBuilder setPublisherName(String publishName) {
        this.publisherName = publishName;
        return this;
    }

    @Override
    public RedPacketBuilder setAcceptName(String acceptName) {
       this.acceptName = acceptName;
       return this;
    }

    @Override
    public RedPacketBuilder setPacketAmount(BigDecimal packetAmount) {
       this.packetAmount = packetAmount;
       return this;
    }

    @Override
    public RedPacketBuilder setPacketType(int packetType) {
        this.packetType = packetType;
        return this;
    }

    @Override
    public RedPacketBuilder setPulishPacketTime(Date pushlishPacketTime) {
       this.pulishPacketTime = pushlishPacketTime;
        return this;
    }

    @Override
    public RedPacketBuilder setOpenPacketTime(Date openPacketTime) {
      this.openPacketTime = openPacketTime;
        return this;
    }


    public RedPacket build() {
        return new RedPacket(publisherName,acceptName,packetAmount,packetType,pulishPacketTime,openPacketTime);
    }
}
           

紅包pojo類(示例):

public class RedPacket {
	
	private String publisherName; //發包人

    private String acceptName; //收包人

    private BigDecimal packetAmount; //紅包金額

    private int packetType; //紅包類型

    private Date pulishPacketTime; //發包時間

    private Date openPacketTime; //搶包時間

    public RedPacket(String publisherName, String acceptName, BigDecimal packetAmount, int packetType, Date pulishPacketTime, Date openPacketTime) {
        this.publisherName = publisherName;
        this.acceptName = acceptName;
        this.packetAmount = packetAmount;
        this.packetType = packetType;
        this.pulishPacketTime = pulishPacketTime;
        this.openPacketTime = openPacketTime;
    }

    //get set 
	public String toString() {
		return "RedPacket [publisherName=" + publisherName + ", acceptName="
				+ acceptName + ", packetAmount=" + packetAmount
				+ ", packetType=" + packetType + ", pulishPacketTime="
				+ pulishPacketTime + ", openPacketTime=" + openPacketTime + "]";
	}
           

使用(示例):

public static void main(String[] args) {
		RedPacket redPacket = RedPacketBuilderImpl.getBulider().setPublisherName("aaa")
				                                               .setAcceptName("bbb")
                                                               .setPacketAmount(new BigDecimal("888"))
                                                               .setPacketType(1)
                                                               .setOpenPacketTime(new Date())
                                                               .setPulishPacketTime(new Date()).build();

		System.out.println(redPacket);
	}
           

對象是怎麼執行個體化的我完全不知道,但是我隻關注裡面的構造過程的某一些資料的過程

建造者模式在mybatis中的應用

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

接着說mybatis的流程

在MyBatis 中負責加載配置檔案的核心類有三個,類圖如下:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

建造器三個核心類 :

 BaseBuilder:所有解析器的父類,包含配置檔案執行個體,為解析檔案提供的一些通用的方

法;

 XMLConfigBuilder: 主要負責解析mybatis-config.xml;

 XMLMapperBuilder: 主要負責解析映射配置 Mapper.xml 檔案;

 XMLStatementBuilder: 主要負責解析映射配置檔案中的SQL 節點;

XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 這三個類在配置檔案加載

過程中非常重要,具體分工如下圖所示:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

這三個類使用了建造者模式對configuration 對象進行初始化,但是沒有使用建造者模式的“肉體”(流式程式設計風格),隻用了靈魂(屏蔽複雜對象的建立過程),把建造者模式演繹成了工廠模式;後面還會對這三個類源碼進行分析;

我們可以了解為 3個Xml是為了填充Configuartion對象而使用的

XMLConfigBuilder的初始化的過程

分析過程 – >

// 1.讀取mybatis配置檔案創SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 
-->org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
-->org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
//這個方法就是讀取xml各種配置,每一個元素都對應着一個節點  XMLConfigBuilder 是繼承BaseBuilder 的在 BaseBuilder中存在Configuration對象的 
           

大概看一些Configuration中比較重要幾個資料結構 :

/*mapper接口的動态代理注冊中心*/
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  /*mapper檔案中增删改查操作的注冊中心*/
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
  /*mapper檔案中配置cache節點的 二級緩存*/
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  
  /*mapper檔案中配置的所有resultMap對象  key為命名空間+ID*/
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  
  /*mapper檔案中配置KeyGenerator的insert和update節點,key為命名空間+ID*/
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
           

關于 Configuration 對象:執行個體化并初始化Configuration 對象是第一個階段的最終目的,是以熟悉configuration 對象是了解第一個階段代碼的核心;configuration 對象的關鍵屬性解析如下:

  • MapperRegistry:mapper 接口動态代理工廠類的注冊中心。在 MyBatis 中,通過mapperProxy 實作 InvocationHandler 接口MapperProxyFactory 用于生成動态代理的執行個體對象;
  • ResultMap:用于解析 mapper.xml 檔案中的resultMap 節點,使用ResultMapping 來封裝id,result 等子元素;
  • MappedStatement:用于存儲mapper.xml 檔案中的 select、insert、update 和 delete 節點,同時還包含了這些節點的很多重要屬性;
  • SqlSource:用于建立 BoundSql,mapper.xml 檔案中的 sql 語句會被解析成 BoundSql 對象,經過解析 BoundSql 包含的語句最終僅僅包含?占位符,可以直接送出給資料庫執行;
  • 需要特别注意的是 Configuration 對象在MyBatis 中是單例的,生命周期是應用級的,換句話說隻要 MyBatis 運作 Configuration 對象就會獨一無二的存在;在 MyBatis 中僅在org.apache.ibatis.builder.xml.XMLConfigBuilder.XMLConfigBuilder(XPathParser, String, Properties)

    中有執行個體化configuration 對象的代碼,如下代碼

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
           

Configuration 對象的初始化(屬性複制),是在建造SqlSessionfactory 的過程中進行的,接下來分析第一個階段的内部流程;

XMl的配置加載過程

可以把第一個階段配置加載過程分解為四個步驟,四個步驟如下圖:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

第一步:通過 SqlSessionFactoryBuilder 建造 SqlSessionFactory,并建立 XMLConfigBuilder 對象 讀 取 MyBatis 核 心 配 置 文 件 ,

//方法:org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties)
 try {
       //讀取配置檔案
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //解析配置檔案得到Configuartion 對象并且傳回SqlSessionFactory
      return build(parser.parse());
     .....
           

第二步:進入XMLConfigBuilder 的 parseConfiguration 方法,對MyBatis 核心配置檔案的各個元素進行解析,讀取元素資訊後填充到 configuration 對象。在 XMLConfigBuildermapperElement()方法中通過 XMLMapperBuilder 讀取所有 mapper.xml 檔案;見方法

使用(示例):

org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode);
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
	//第三步:XMLMapperBuilder 的核心方法為 configurationElement (XNode ),該方法對
//mapper.xml 配置檔案的各個元素進行解析,讀取元素資訊後填充到 configuration 對象 
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
     //解析<properties>節點
      propertiesElement(root.evalNode("properties"));
      //解析<settings>節點
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析<typeAliases>節點
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>節點
      pluginElement(root.evalNode("plugins"));
      //解析<objectFactory>節點
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析<objectWrapperFactory>節點
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析<reflectorFactory>節點
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//将settings填充到configuration
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析<environments>節點
      environmentsElement(root.evalNode("environments"));
      //解析<databaseIdProvider>節點
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析<typeHandlers>節點
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>節點
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
           

第三步:XMLMapperBuilder 的核心方法為 configurationElement (XNode ),該方法對mapper.xml 配置檔案的各個元素進行解析,讀取元素資訊後填充到 configuration 對象。

org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 
   -->org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
    public void parse() {
        //判斷是否已經加載該配置檔案
        if (!configuration.isResourceLoaded(resource)) {
            //處理mapper節點
            configurationElement(parser.evalNode("/mapper"));
            /*将mapper檔案添加到configuration.loadedResources中*/
            configuration.addLoadedResource(resource);
            /*注冊mapper接口 找到Class類與 xml檔案所處的位置 重點分析 */
            bindMapperForNamespace();
        }
        //處了解析失敗的ResultMap節點
        parsePendingResultMaps();
        //處了解析失敗的CacheRef節點
        parsePendingCacheRefs();
        //處了解析失敗的Sql語句節點
        parsePendingStatements();
    } 
           

先來檢視緩存的方法

org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement
	->org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache 
	/通過builderAssistant建立緩存對象,并添加至configuration
  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
	//經典的建造起模式,建立一個cache對象
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //将緩存添加至configuration,注意二級緩存以命名空間為機關進行劃分
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }
  //建造者的模式關注點 build方法,把一些細節都藏在了方法裡對外隐藏了建立的細節  
  //org.apache.ibatis.mapping.CacheBuilder#build  
  // --> org.apache.ibatis.mapping.CacheBuilder#setStandardDecorators 
  	//裝飾器模式的應用 對cache指派上各種功能 
  	  /*設定讀寫屬性*/
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      /*日志能力*/
      cache = new LoggingCache(cache);
      /*同步*/
      cache = new SynchronizedCache(cache);
      /*加上阻塞*/
      if (blocking) {
        cache = new BlockingCache(cache);
      }
           

在XMLMapperBuilder 解析過程中,有四個點需要注意:

  1. resultMapElements(List)方法用于解析 resultMap 節點,這個方法非常重要,一定要跟源碼了解;解析完之後資料儲存在 configuration 對象的 resultMaps 屬性中;
//解析resultMap節點,實際就是解析sql查詢的字段與pojo屬性之間的轉化規則
    private void resultMapElements(List<XNode> list) throws Exception {
        //周遊所有的resultmap節點
        for (XNode resultMapNode : list) {
            try {
                //解析具體某一個resultMap節點
                resultMapElement(resultMapNode);
            } catch (IncompleteElementException e) {
                // ignore, it will be retried
            }
        }
    }
    org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode, java.util.List<org.apache.ibatis.mapping.ResultMapping>) 
    
           

resultMapping 的資料結構

/*引用的configuration對象*/
  private Configuration configuration;
  private String property;//對應節點的property屬性
  private String column;//對應節點的column屬性
  private Class<?> javaType;//對應節點的javaType屬性
  private JdbcType jdbcType;//對應節點的jdbcType屬性
  private TypeHandler<?> typeHandler;//對應節點的typeHandler屬性
  private String nestedResultMapId;//對應節點的resultMap屬性,嵌套結果時使用
  private String nestedQueryId;//對應節點的select屬性,嵌套查詢時使用
  private Set<String> notNullColumns;//對應節點的notNullColumn屬性
  private String columnPrefix;//對應節點的columnPrefix屬性
  private List<ResultFlag> flags;//标志,id 或者 constructor
  private List<ResultMapping> composites;
  private String resultSet;//對應節點的resultSet屬性
  private String foreignColumn;//對應節點的foreignColumn屬性
  private boolean lazy;//對應節點的fetchType屬性,是否延遲加載
           
Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結
//執行個體化resultMap并将其注冊到configuration對象
  public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
	 //完善id,id的完整格式是"namespace.id"
    id = applyCurrentNamespace(id, false);
    //獲得父類resultMap的完整id
    extend = applyCurrentNamespace(extend, true);

    //針對extend屬性的處理
    if (extend != null) {
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      //添加需要被繼承下來的resultMapping對象結合
      resultMappings.addAll(extendedResultMappings);
    }
    //通過建造者模式執行個體化resultMap,并注冊到configuration.resultMaps中
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }
           
  1. XMLMapperBuilder 中在執行個體化二級緩存(見cacheElement(XNode))、執行個體化 resultMap(見resultMapElements(List))過程中都使用了建造者模式,而且是建造者模式的典型應用;
  2. XMLMapperBuilder 和 XMLMapperStatmentBuilder 有 自 己 的 “ 秘 書 ”MapperBuilderAssistant。XMLMapperBuilder 和XMLMapperStatmentBuilder 負責解析讀取配置檔案裡面的資訊,MapperBuilderAssistant 負責将資訊填充到 configuration。将檔案解析和資料的填充的工作分離在不同的類中,符合單一職責原則;
  3. 在 buildStatementFromContext(List)方法中,建立 XMLStatmentBuilder 解析Mapper.xml 中select、insert、update、delete 節點
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>) 
 //處理所有的sql語句節點并注冊至configuration對象
    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
            //建立XMLStatementBuilder 專門用于解析sql語句節點
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                //解析sql語句節點
                statementParser.parseStatementNode();
            } catch (IncompleteElementException e) {
                configuration.addIncompleteStatement(statementParser);
            }
        }
    } 
   .....
           

第四步:在 XMLStatmentBuilder 的 parseStatementNode()方法中,對 Mapper.xml 中 select,insert、update、delete 節點進行解析,并調用 MapperBuilderAssistant 負責将資訊填充到configuration。在了解 parseStatementNod()方法之前,有必要了解 MappedStatement,這個

類 用 于 封 裝 select 、 insert 、 update 、 delete 節 點 的 信 息 ; 如 下 圖 所 示 :

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

大概的流程圖

Mybatis 的接口層

第二個階段使用到的第一個對象就是 SqlSession,SqlSession 是MyBaits 對外提供的最關鍵的核心接口,通過它可以執行資料庫讀寫指令、擷取映射器、管理事務等;SqlSession意味着用戶端與資料庫的一次連接配接,用戶端對資料庫的通路請求都是由SqlSession 來處理SqlSession 由SqlSessionFactory 建立,每個SqlSession 都會引用SqlSessionFactory 中全局唯一單例存在的configuration 對象;如下圖所示:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

要 深 入 理 解 SqlSession 就 得 深 入 到 源 碼 進 行 學 習 , SqlSession 默 認 實 現 類 為org.apache.ibatis.session.defaults.DefaultSqlSession,解讀如下:

(1) SqlSession 是MyBatis 的門面,是MyBatis 對外提供資料通路的主要API,執行個體代碼

@Test
    // ibatis程式設計模型 本質分析
    public void originalOperation() throws IOException {
        // 2.擷取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.執行查詢語句并傳回結果 通過nameSpace 加id來确認接口
        TUser user = sqlSession.selectOne("com.enjoylearning.mybatis.mapper.TUserMapper.selectByPrimaryKey", 2);
        System.out.println(user.toString());
    }
           

(2) 實際上Sqlsession 的功能都是基于Executor 來實作的,遵循了單一職責原則,例如 在 SqlSession 中的各種查詢形式,最終會把請求轉發到 Executor.query 方法,如下圖所

示:

Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

政策模式 : 政策模式(Strategy Pattern)政策模式定義了一系列的算法,并将每一個算法封裝起來,而且使他們可以互相替換,讓算法獨立于使用它的客戶而獨立變化。Spring 容器中使用配置可以靈活的替換掉接口的實作類就是政策模式最常見的應用。類圖如下:

  • Context:算法調用者,使用 setStrategy 方法靈活的選擇政策(strategy);
  • Strategy:算法的統一接口;
  • ConcreteStrategy:算法的具體實作;
    Mybatis源碼分析 反射子產品以及設計模式在mybatis中的應用Mybatis反射子產品的解讀與分析orm架構查詢資料過程一 反射子產品的大概分析二、MyBatis 流程概述總結

    政策模式的使用場景:

    (1) 針對同一類型問題的多種處理方式,僅僅是具體行為有差别時;

    (2) 出現同一抽象類有多個子類,而又需要使用 if-else 或者 switch-case 來選擇具體子類時。

SqlSessionFactory

sqlSessionFactory 使 用 工 廠 模 式 創 建 SqlSession , 其 默 認 的 實 現 類 為DefaultSqlSessionFactory , 其 中 獲 取 SqlSession 的 核 心 方 法 為openSessionFromDataSource(ExecutorType, TransactionIsolationLevel, boolean),在這個方法中從 configuration 中擷取的 TransactionFactory 是典型的政策模式的應用。運作期,TransactionFactory 接口的實作,是由配置檔案配置決定的,可配置選項包括:JDBC、Managed,可根據需求靈活的替換TransactionFactory 的實作

//從資料源擷取資料庫連接配接
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    	//擷取mybatis配置檔案中的environment對象
      final Environment environment = configuration.getEnvironment();
      //從environment擷取transactionFactory對象
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //建立事務對象 拿到資料庫連接配接池 重點分析  不管是Pull 還是UnPull都是實作了DataSource  而且可以通過配置檔案來配置
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根據配置建立executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //建立DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
           
<environments default="development">
		<!-- 環境配置1,每個SqlSessionFactory對應一個環境 -->
		<environment id="development">
			<transactionManager type="JDBC" />
<!--            靈活配置根據需要替換實作-->
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc_driver}" />
				<property name="url" value="${jdbc_url}" />
				<property name="username" value="${jdbc_username}" />
				<property name="password" value="${jdbc_password}" />
			</dataSource>
		</environment>


	</environments>
           

總結

今天寫了mybatis的反射子產品的分析與源碼解析,了解了幾個設計模式,并且mybatis是怎麼使用它的,mybatis是怎麼生成相關的資訊的resultMap的相關資訊的,怎麼指向sql的,與ibatis的差別等等