mybatis工做流程

  先來看一下MyBatis 的編程式使用的方法:html

public void testMapper() throws IOException {
  String resource = "mybatis-config.xml";
  InputStream inputStream = Resources.getResourceAsStream(resource);
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  SqlSession session = sqlSessionFactory.openSession();
  try {
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlogById(1);
    System.out.println(blog);
  } finally {
    session.close();
  }
}

  咱們再來捋MyBatis 的主要工做流程:首先在MyBatis 啓動的時候咱們要去解析配置文件,包括全局配置文件和映射器配置文件,這裏麪包含了咱們怎麼控制MyBatis 的行爲,和咱們要對數據庫下達的指令,也就是咱們的SQL 信息。咱們會把它們解析成一個Configuration 對象。接下來就是咱們操做數據庫的接口,它在應用程序和數據庫中間,表明咱們跟數據庫之間的一次鏈接:這個就是SqlSession 對象。咱們要得到一個會話, 必須有一個會話工廠SqlSessionFactory 。SqlSessionFactory 裏面又必須包含咱們的全部的配置信息,因此咱們會經過一個Builder 來建立工廠類。sql

  咱們知道,MyBatis 是對JDBC 的封裝,也就是意味着底層必定會出現JDBC 的一些核心對象,好比執行SQL 的Statement,結果集ResultSet。在Mybatis 裏面,SqlSession 只是提供給應用的一個接口,還不是SQL 的真正的執行對象。咱們經過查看SqlSession源碼發現,SqlSession 持有了一個Executor 對象,用來封裝對數據庫的操做。在執行器Executor 執行query 或者update 操做的時候咱們建立一系列的對象,來處理參數、執行SQL、處理結果集,這裏咱們把它簡化成一個對象:StatementHandler,這個就是MyBatis 主要的工做流程,如圖:數據庫

MyBatis 架構分層與模塊劃分:

  在MyBatis 的主要工做流程裏面,不一樣的功能是由不少不一樣的類協做完成的,它們分佈在MyBatis jar 包的不一樣的package 裏面。咱們來看一下MyBatis 的jar 包(基於3.5.1)編程

跟Spring 同樣,MyBatis 按照功能職責的不一樣,全部的package 能夠分紅不一樣的工做層次。咱們能夠把MyBatis 的工做流程類比成餐廳的服務流程。緩存

  第一個是跟客戶打交道的服務員,它是用來接收程序的工做指令的,咱們把它叫作接口層。session

  第二個是後臺的廚師,他們根據客戶的點菜單,把原材料加工成成品,而後傳到窗口。這一層是真正去操做數據的,咱們把它叫作核心層。mybatis

  最後就是餐廳也須要有人作後勤(好比清潔、採購、財務),來支持廚師的工做和整個餐廳的運營。咱們把它叫作基礎層。架構

來看一下這張圖,咱們根據剛纔的分層,和大致的執行流程,作了這麼一個總結。固然,從不一樣的角度來描述,架構圖的劃分有所區別,這張圖畫起來也有不少形式。咱們先從整體上創建一個印象。app

接口層:

  首先接口層是咱們打交道最多的。核心對象是SqlSession,它是上層應用和MyBatis打交道的橋樑,SqlSession 上定義了很是多的對數據庫的操做方法。接口層在接收到調用請求的時候,會調用核心處理層的相應模塊來完成具體的數據庫操做。ide

核心處理層:

  接下來是核心處理層。既然叫核心處理層,也就是跟數據庫操做相關的動做都是在這一層完成的。核心處理層主要作了這幾件事:

  1. 把接口中傳入的參數解析而且映射成JDBC 類型;
  2. 解析xml 文件中的SQL 語句,包括插入參數,和動態SQL 的生成;
  3. 執行SQL 語句;
  4. 處理結果集,並映射成Java 對象。

  插件也屬於核心層,這是由它的工做方式和攔截的對象決定的。

基礎支持層:

  最後一個就是基礎支持層。基礎支持層主要是一些抽取出來的通用的功能(實現複用),用來支持核心處理層的功能。好比數據源、緩存(請點擊跳轉至緩存詳解)、日誌、xml 解析、反射、IO、事務等等這些功能。

  這個就是MyBatis 的主要工做流程和架構分層。

從以上的編程式的例子來看,mybatis的工做流程大體能夠分爲一下四步:

  1. 經過建造者模式建立一個工廠類,定位,加載,解析配置文件的就是在這一步完成的,包括mybatis-config.xml 和Mapper 適配器文件。
  2. 經過SqlSessionFactory 建立一個SqlSession。
  3. 得到Mapper 對象。
  4. 調用接口方法(insert,delete,update,select)。

第一步配置解析過程:

  定位資源位置,加載資源,將xml加載成流這裏就不分析了。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

  首先咱們要清楚的是配置解析的過程所有隻解析了兩種文件。一個是mybatis-config.xml 全局配置文件。另外就是可能有不少個的Mapper.xml 文件,也包括在Mapper 接口類上面定義的註解。咱們從mybatis-config.xml 開始。在以前已經分析了核心配置了,大概明白了MyBatis 有哪些配置項,和這些配置項的大體含義。這裏咱們再具體看一下這裏面的標籤都是怎麼解析的,解析的時候作了什麼。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

  首先咱們new 了一個SqlSessionFactoryBuilder,很是明顯的建造者模式,它裏面定義了不少個build 方法的重載,最終返回的是一個SqlSessionFactory 對象(單例模式)。咱們點進去build 方法。這裏面建立了一個XMLConfigBuilder 對象(Configuration 對象也是這個時候建立的)。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
// 建立全局文件解析器 XMLConfigBuilder parser
= new XMLConfigBuilder(inputStream, environment, properties);
// 調用解析,解析完構建默認的SessionFactory var5
= this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException var13) { } } return var5; }

  XMLConfigBuilder 是抽象類BaseBuilder 的一個子類,專門用來解析全局配置文件,針對不一樣的構建目標還有其餘的一些子類,好比:XMLMapperBuilder:解析Mapper 映射器,XMLStatementBuilder:解析增刪改查標籤。

  根據咱們解析的文件流,這裏後面兩個參數都是空的,建立了一個parser。這裏有兩步,第一步是調用parser 的parse()方法,它會返回一個Configuration類。也就是配置文件裏面全部的信息都會放在Configuration 裏面。Configuration 類裏面有不少的屬性,有不少是跟config 裏面的標籤直接對應的。

public Configuration parse() {
   // 判斷是否已經解析過
if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true;
      // 解析的開始
this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }

  首先會檢查是否是已經解析過,也就是說在應用的生命週期裏面,config 配置文件只須要解析一次,生成的Configuration 對象也會存在應用的整個生命週期中。接下來就是parseConfiguration 方法:

  這下面有十幾個方法,對應着config 文件裏面的全部一級標籤。MyBatis 全局配置文件的順序不能夠顛倒。

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
//解析settings標籤
this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }

  在settings標籤的解析中,咱們就能夠看到對應官網上介紹的各類配置,及其默認值,全部的值,都會賦值到Configuration 的屬性裏面去。簡單的來看一下:

private void settingsElement(Properties props) {
        this.configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        this.configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
        this.configuration.setCacheEnabled(this.booleanValueOf(props.getProperty("cacheEnabled"), true));//二級緩存
        this.configuration.setProxyFactory((ProxyFactory)this.createInstance(props.getProperty("proxyFactory")));
        this.configuration.setLazyLoadingEnabled(this.booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
        this.configuration.setAggressiveLazyLoading(this.booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
        this.configuration.setMultipleResultSetsEnabled(this.booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
        this.configuration.setUseColumnLabel(this.booleanValueOf(props.getProperty("useColumnLabel"), true));
        this.configuration.setUseGeneratedKeys(this.booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        this.configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));//默認執行器類型
        this.configuration.setDefaultStatementTimeout(this.integerValueOf(props.getProperty("defaultStatementTimeout"), (Integer)null));
        this.configuration.setDefaultFetchSize(this.integerValueOf(props.getProperty("defaultFetchSize"), (Integer)null));
        this.configuration.setMapUnderscoreToCamelCase(this.booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        this.configuration.setSafeRowBoundsEnabled(this.booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
        this.configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));//本地緩存級別
        this.configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        this.configuration.setLazyLoadTriggerMethods(this.stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
        this.configuration.setSafeResultHandlerEnabled(this.booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
        this.configuration.setDefaultScriptingLanguage(this.resolveClass(props.getProperty("defaultScriptingLanguage")));
        this.configuration.setDefaultEnumTypeHandler(this.resolveClass(props.getProperty("defaultEnumTypeHandler")));
        this.configuration.setCallSettersOnNulls(this.booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        this.configuration.setUseActualParamName(this.booleanValueOf(props.getProperty("useActualParamName"), true));
        this.configuration.setReturnInstanceForEmptyRow(this.booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        this.configuration.setLogPrefix(props.getProperty("logPrefix"));
        this.configuration.setConfigurationFactory(this.resolveClass(props.getProperty("configurationFactory")));
}

  最後就是<mappers>標籤的解析,對應匹配以下四種方式:

<!-- 使用相對於類路徑的資源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用徹底限定資源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 將包內的映射器接口實現所有註冊爲映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
<!-- 使用映射器接口實現類的徹底限定類名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

  首先會判斷是否是接口,只有接口才解析;而後判斷是否是已經註冊了,單個Mapper重複註冊會拋出異常。若是是以資源位置及URL的配置方式,還須要建立Mapper解析器解析。

private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();
            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
//配置包的方式
if ("package".equals(child.getName())) { resource = child.getStringAttribute("name");
               //解析完加入全局配置類,填充屬性
this.configuration.addMappers(resource); } else { resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream inputStream;
//配置以資源的方式
if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); inputStream = Resources.getResourceAsStream(resource); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse();
//配置以URL的方式 }
else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); inputStream = Resources.getUrlAsStream(url); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments()); mapperParser.parse();
// 配置以class單個接口 方式 }
else {//都沒配置報異常 if (resource != null || url != null || mapperClass == null) { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } Class<?> mapperInterface = Resources.classForName(mapperClass); this.configuration.addMapper(mapperInterface); } } } return; } } }

  XMLMapperBuilder.parse()方法,是對Mapper 映射器的解析。裏面有兩個方法:configurationElement()—— 解析全部的子標籤, 其中 buildStatementFromContext()最終得到MappedStatement 對象。bindMapperForNamespace()——把namespace(接口類型)和工廠類綁定起來。

  不管是按package 掃描,仍是按接口掃描,最後都會調用到MapperRegistry 的addMapper()方法。最後,MapperRegistry 也會放到Configuration 裏面去。MapperRegistry 裏面維護的實際上是一個Map 容器,存儲接口和代理工廠的映射關系。

public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {//判斷是否接口
            if (this.hasMapper(type)) { //是否已經註冊
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {//利用Map來維護mapper
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();//解析
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {//註冊過程出現異常,移除
                    this.knownMappers.remove(type);
                }
            }
        }
}

  除了映射器文件,在這裏也會去解析Mapper 接口方法上的註解。在addMapper()方法裏面建立了一個MapperAnnotationBuilder,咱們點進去看一下parse()方法。parseCache() 和parseCacheRef() 方法實際上是對@CacheNamespace 和@CacheNamespaceRef 這兩個註解的處理。parseStatement()方法裏面的各類getAnnotation(),都是對註解的解析,好比@Options,@SelectKey,@ResultMap 等等。最後一樣會解析成MappedStatement 對象,也就是說在XML 中配置,和使用註解配置,最後起到同樣的效果。

public void parse() {
        String resource = this.type.toString();
        if (!this.configuration.isResourceLoaded(resource)) {
            this.loadXmlResource();//加載XML資源
            this.configuration.addLoadedResource(resource);
            this.assistant.setCurrentNamespace(this.type.getName());
            this.parseCache();//解析@CacheNamespace註解
            this.parseCacheRef();//解析@CacheNamespaceRef註解
            Method[] methods = this.type.getMethods();
            Method[] var3 = methods;
            int var4 = methods.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Method method = var3[var5];
                try {
                    if (!method.isBridge()) {
                        this.parseStatement(method);//處理各類註解
                    }
                } catch (IncompleteElementException var8) {
                    this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        this.parsePendingMethods();
}

  最後會將這些解析的結果填充倒Configuration中。在這一步,咱們主要完成了config 配置文件、Mapper 文件、Mapper 接口上的注解的解析。咱們獲得了一個最重要的對象Configuration,這裏面存放了所有的配置信息,它在屬性裏面還有各類各樣的容器。最後,返回了一個DefaultSqlSessionFactory,裏面持有了Configuration 的實例。以下是第一階段的時序圖:

 第二步會話建立過程:

  這是第二步, 咱們跟數據庫的每一次鏈接, 都須要建立一個會話, 咱們用openSession()方法來建立。

SqlSession session = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;

DefaultSqlSession var8;
try {
//從先前所解析的配置中獲取環境
Environment environment = this.configuration.getEnvironment();
//獲取事務工廠類
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//建立事務
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//建立執行器類 默認SIMPLE
Executor executor = this.configuration.newExecutor(tx, execType);
//建立默認的SqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}

  這個會話裏面,須要包含一個Executor 用來執行SQL。Executor 又要指定事務類型和執行器的類型。因此咱們會先從Configuration 裏面拿到Enviroment,Enviroment 裏面就有事務工廠。建立事務會有如下兩種選擇:

  若是配置的是JDBC,則會使用Connection 對象的commit()、rollback()、close()管理事務。若是配置成MANAGED,會把事務交給容器來管理,好比JBOSS,Weblogic。若是是Spring + MyBatis , 則沒有必要配置, 由於咱們會直接在applicationContext.xml 裏面配置數據源和事務管理器,覆蓋MyBatis 的配置。

  咱們知道,Executor 的基本類型有三種:SIMPLE、BATCH、REUSE,默認是SIMPLE(settingsElement()讀取默認值),他們都繼承了抽象類BaseExecutor。

  • SimpleExecutor:每執行一次update 或select,就開啓一個Statement 對象,用完馬上關閉Statement 對象。
  • ReuseExecutor:執行update 或select,以sql 做爲key 查找Statement 對象,存在就使用,不存在就建立,用完後,不關閉Statement 對象,而是放置於Map 內,供下一次使用。簡言之,就是重複使用Statement 對象。
  • BatchExecutor:執行update(沒有select,JDBC 批處理不支持select),將所有sql 都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement 對象,每一個Statement 對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC 批處理相同。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//判斷使用的執行器類型,之因此判斷兩次是防止手賤的人把執行器配置爲空 executorType
= executorType == null ? this.defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Object executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (this.cacheEnabled) {//是否啓用二級緩存,啓用了進行包裝 executor = new CachingExecutor((Executor)executor); }
//插件包裝,轉換成攔截器鏈interceptorChain,對於插件我下一篇博客會詳細介紹 Executor executor
= (Executor)this.interceptorChain.pluginAll(executor); return executor; }

  若是配置了cacheEnabled=ture,會用裝飾器模式對executor 進行包裝:newCachingExecutor(executor)。最終返回DefaultSqlSession,屬性包括Configuration、Executor 對象。

  總結:建立會話的過程,咱們得到了一個DefaultSqlSession,裏面包含了一個Executor,它是SQL 的執行者。

 

第三步得到Mapper 對象:

  如今咱們已經有一個DefaultSqlSession 了,必須找到Mapper.xml 裏面定義的Statement ID,才能執行對應的SQL 語句。找到Statement ID 有兩種方式:一種是直接調用session 的方法,在參數裏面傳入Statement ID,這種方式屬於硬編碼,咱們沒辦法知道有多少處調用,修改起來也很麻煩。另外一個問題是若是參數傳入錯誤,在編譯階段也是不會報錯的,不利於預先發現問題。

Blog blog = (Blog) session.selectOne("com.wuzz.mapper.BlogMapper.selectBlogById", 1);

  因此在MyBatis 後期的版本提供了第二種方式,就是定義一個接口,而後再調用Mapper 接口的方法。因爲咱們的接口名稱跟Mapper.xml 的namespace 是對應的,接口的方法跟statement ID 也都是對應的,因此根據方法就能找到對應的要執行的SQL。

BlogMapper mapper = session.getMapper(BlogMapper.class);

  在這裏咱們主要研究一下Mapper 對象是怎麼得到的,它的本質是什麼。DefaultSqlSession 的getMapper()方法,調用了Configuration 的getMapper()方法。繼而調到 MapperRegistry.getMapper:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}

  咱們知道,在解析mapper 標籤和Mapper.xml 的時候已經把接口類型和類型對應的MapperProxyFactory 放到了一個Map 中。獲取Mapper 代理對象,其實是從Map 中獲取對應的工廠類後,最終經過代理模式返回代理對象。

  那麼JDK 動態代理和MyBatis 用到的JDK 動態代理有什麼區別呢?JDK 動態代理代理,在實現了InvocationHandler 的代理類裏面,須要傳入一個被代理對象的實現類。而在mybatis裏面,咱們並無實現了InvocationHandler 的代理類。不須要實現類的緣由:咱們只須要根據接口類型+方法的名稱,就能夠找到Statement ID 了,而惟一要作的一件事情也是這件,因此不須要實現類。在MapperProxy裏面直接執行邏輯(也就是執行SQL)就能夠。

  得到Mapper 對象的過程,實質上是獲取了一個MapperProxy 的代理對象。MapperProxy 中有sqlSession、mapperInterface、methodCache。

第四步執行sql:

  因爲全部的Mapper 都是MapperProxy 代理對象,因此任意的方法都是執行MapperProxy 的invoke()方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {//首先判斷是否須要去執行SQL,仍是直接執行方法。
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            //Object 自己的方法和Java 8 中接口的默認方法不須要去執行SQL。
            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
      //獲取緩存  這裏加入緩存是爲了提高MapperMethod 的獲取速度
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

   接下來又調用了mapperMethod 的execute 方法,咱們debug來看一下:

  MapperMethod 裏面主要有兩個屬性, 一個是SqlCommand (SQL命令), 一個是MethodSignature(方法簽名),這兩個都是MapperMethod 的內部類。另外定義了多個execute()方法。

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;//根據不一樣的type 和返回類型 調用sqlSession 的insert()、update()、delete()、selectOne ()方法
        switch(this.command.getType()) {
        case INSERT:
       // 調用convertArgsToSqlCommandParam()將參數轉換爲SQL 的參數。 param
= this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT://查詢的方法又要根據返回類型進行區分執行 if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH://刷新statement result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }

  咱們以查詢  (mapper1.selectBlogById(1002))  爲例 會走以下代碼,會走到selectOne()方法。

public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

  這裏拋得異常是否是很熟悉,常常有小夥伴在開發過程當中會發現sql執行報這個異常,是由於定義的sql是查詢的一個結果,但是返回了多個,這裏會執行selectList(statement, parameter):

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {//獲取初始化階段所封裝的對象先根據command name(Statement ID)從Configuration中拿到MappedStatement
       MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }
        return var5;
}

  這個ms 上面有咱們在xml 中配置的全部屬性,包括id、statementType、sqlSource、useCache、入參、出參等等。

  而後執行了Executor 的query()方法。前面咱們說到了Executor 有三種基本類型,SIMPLE/REUSE/BATCH,還有一種包裝類型,CachingExecutor。那麼在這裏到底會選擇哪種執行器呢?咱們要回過頭去看看DefaultSqlSession 在初始化的時候是怎麼賦值的,這個就是咱們的會話建立過程。若是啓用了二級緩存,就會先調用CachingExecutor 的query()方法,裏面有緩存相關的操做,而後纔是再調用基本類型的執行器,好比默認的SimpleExecutor。在沒有開啓二級緩存的狀況下,先會走到BaseExecutor 的query()方法(不然會先走到CachingExecutor)。

  BaseExecutor:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

  從Configuration 中獲取MappedStatement, 而後從BoundSql 中獲取SQL 信息,建立CacheKey。這個CacheKey 就是緩存的Key。而後再調用另外一個query()方法。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {//queryStack 用於記錄查詢棧,防止遞歸查詢重複處理緩存。flushCache=true 的時候,會先清理本地緩存(一級緩存)
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
//清除緩存
this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null ? (List)this.localCache.getObject(key) : null; if (list != null) {//從緩存中獲取數據 this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else {//若是沒有緩存,會從數據庫查詢:queryFromDatabase() list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { Iterator var8 = this.deferredLoads.iterator(); while(var8.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next(); deferredLoad.load(); } this.deferredLoads.clear();
//若是LocalCacheScope == STATEMENT,會清理本地緩存。
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }

  從數據庫查詢(queryFromDatabase):

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     // 先在緩存用佔位符佔位。執行查詢後,移除佔位符,放入數據。
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); List list; try {//執行Executor 的doQuery();默認是SimpleExecutor。 list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { this.localCache.removeObject(key); } this.localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list; }

  SimpleExecutor.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

  在configuration.newStatementHandler()中,new 一個StatementHandler,先獲得RoutingStatementHandler。RoutingStatementHandler 裏面沒有任何的實現, 是用來建立基本的StatementHandler 的。這裏會根據MappedStatement 裏面的statementType 決定StatementHandler 的類型。默認是PREPARED ( STATEMENT 、PREPARED 、CALLABLE)。

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        switch(ms.getStatementType()) {
        case STATEMENT:
            this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
}

  StatementHandler 裏面包含了處理參數的ParameterHandler 和處理結果集的ResultSetHandler。這兩個對象都是在上面new 的時候建立的。

this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);

  這三個對象都是能夠被插件攔截的四大對象之一,因此在建立以後都要用攔截器進行包裝的方法。(對於詳細的插件機制將在下篇博客中詳細介紹)

StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);

  四大對象還有一個是誰?在何時建立的?(Executor)在第二步建立會話的時候建立的。用new 出來的StatementHandler 建立Statement 對象——prepareStatement()方法對語句進行預編譯,處理參數。執行的StatementHandler 的query()方法,RoutingStatementHandler 的query()方法。

delegate 委派,最終執行PreparedStatementHandler 的query()方法。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleResultSets(ps);
}

  執行PreparedStatement 的execute()方法,後面就是JDBC 包中的PreparedStatement 的執行了。

  最後經過 resultSetHandler.handleResultSets(ps) 處理結果集。

 

   最後總結一下整個源碼的過程所涉及的核心類:

相關文章
相關標籤/搜索