mybatis源碼分析(方法調用過程)

十一月月底,宿舍樓失火啦,搞得20多天沒有網,目測直到放假也不會來了。。。

?

正題

嗯~,其實閱讀源碼不是為了應付面試,更重要的讓你知道,大師是怎樣去寫代碼的,同樣是用Java,為啥Clinton Begin寫的叫源碼,而你寫只能叫代碼。

最簡單的入門代碼:

先讀取配置文件流,然后構造個SqlSessionFactory,然后開啟一個SqlSession,指定statement,調用查詢方法,返回結果。那么,你知道他是怎樣實現的嗎

?SqlSessionFactoryBuilder.build 方法

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.
      }}}

將輸入流傳入 XMLConfigBuilder 的構造方法來創建一個 XMLConfigBuilder 對象, 調用 XMLConfigBuilder parse 方法進行解析配置文件,返回一個 Configuration 對象

  public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

?

將返回的 Configuration 對象傳入另外一個重載的 build 方法,實際上是傳入了 DefaultSqlSessionFactory 的構造方法,返回 sqlSessionFactory

我比較關心的是 XMLConfigBuilder parse 方法,都干了什么事情

首先進入 XMLConfigBuilder 的構造方法后, 真正使用配置文件輸入流的是 XPathParser, 它是負責解析 XML文件元素節點的, 通俗地講, XpathParser 負責將原料加工成零件, XMLConfigBuilder 負責按照工序組裝零件成一個產品。

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}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;}

?

經過構造方法初始化好XPathParser后,就要進入parse方法了。Parse 方法里有個判斷,如果已經解析過了,就會拋出異常,如果沒解析,就將解析標志設為 true。接著調用parseConfiguration?

  public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}private void parseConfiguration(XNode root) {try {Properties settings = settingsAsPropertiess(root.evalNode("settings"));//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));loadCustomVfs(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectionFactoryElement(root.evalNode("reflectionFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

?

parseConfiguration 就是工序圖, 組裝產品一定是按照一定順序的, 所以這也是建造者模式的核心。
比如:第一個是全局設置 settings,第二個是屬性文件,第三個是別名。在這里我們看 environmentsElement
很明顯它是來構建 Environment 的, 也就是我們配置的數據源信息。

  private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));    // 構建事務工廠DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));    // 構建數據源工廠DataSource dataSource = dsFactory.getDataSource();Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());}}}}

主要有兩大塊: transactionManagerElement dataSourceElement

  private TransactionFactory transactionManagerElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties props = context.getChildrenAsProperties();TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a TransactionFactory.");}private DataSourceFactory dataSourceElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties props = context.getChildrenAsProperties();DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a DataSourceFactory.");}

注意看: resolveClass(type).newInstance(), 這不就是反射嗎?

而那個 type 則是我們配置文件里面配置的,比如 jdbc 或是 manage, 對應 JdbcTransactionFactory ManagedTransactionFactory

Upooled Pooled對應 UnpooledDataSourceFactory PooledDataSourceFactory

返回 environmentsElement 方法, 我們還看到 Environment 有個 Builder 類, 準確來說是靜態內部類。

          Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());

?

具體的Environment類:

public final class Environment {private final String id;private final TransactionFactory transactionFactory;private final DataSource dataSource;public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {if (id == null) {throw new IllegalArgumentException("Parameter 'id' must not be null");}if (transactionFactory == null) {throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");}this.id = id;if (dataSource == null) {throw new IllegalArgumentException("Parameter 'dataSource' must not be null");}this.transactionFactory = transactionFactory;this.dataSource = dataSource;}public static class Builder {private String id;private TransactionFactory transactionFactory;private DataSource dataSource;public Builder(String id) {this.id = id;}public Builder transactionFactory(TransactionFactory transactionFactory) {this.transactionFactory = transactionFactory;return this;}public Builder dataSource(DataSource dataSource) {this.dataSource = dataSource;return this;}public String id() {return this.id;}public Environment build() {return new Environment(this.id, this.transactionFactory, this.dataSource);}}public String getId() {return this.id;}public TransactionFactory getTransactionFactory() {return this.transactionFactory;}public DataSource getDataSource() {return this.dataSource;}}

?

那么這里有一個設計模式的問題。為什么Environment里面要搞一個Builder類呢?直接使用構造方法不也可以達到相同的目的嗎?

1. 首先, 用內部類是因為內部類與外部類有一定的關系, 往往只有該外部類調用此內部類。 靜態內部類
只能訪問靜態的成員變量和方法,不能訪問非靜態變量的方法。但是普通內部類可以訪問任意外部類
的成員變量和方法。靜態內部類可以聲明普通成員變量和方法,但是普通內部類不能聲明 static 變量
或方法。
靜態內部類: Inner I = new Outer.Inner();
普通內部類: Outer o = new Outer(); Inner I = o.new Inner();
2. 另外, 靜態都是用來修飾類的內部成員的, 比如靜態方法, 靜態成員變量。 靜態方法不能訪問非靜態
變量和非靜態方法。 Static 不能修飾局部變量。
3. 總結:如果類的構造函數有多個參數,設計這樣的類時, 最好使用 Builder 模式, 特別是大多數參數
都是可選的時候。如果現在不能確定參數的個數,最好一開始就使用建造者模式。?

?到此,SqlSessionFactoryBuilder.build方法的作用是:解析配置文件,構建唯一的Configuration對象,構建全局唯一并且線程安全的SqlSessionFactory。

?

SqlSessionFactory的openSession方法

?進入openSession方法

  @Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}

?

會發現它調用的是另外一個方法。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);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();}}

?

看try塊第一行,獲得的Environment是前面由XMLConfigBuilder裝配到Configuration里面的。第二三行的事務工廠和數據源都是在解析配置文件期間裝配到Environment里面的。

到第四行,這是個新東西Executor,簡單來說它是真正執行CRUD操作的工具,給我們提供的SqlSession僅僅是個用戶接口。Executor也是由Configuration對象來創建的,可見Configuration是多么重要。

我們知道事務操作必不可少,所以Executor的創建必須有Transaction對象。

第五行,就是創建SqlSession了,DefaultSqlSession是一個具體實現類。我們可以看到它把Executor傳進去了,那么就不難發現,SqlSession不過是件衣裳。

接下來看SqlSession調用過程

?

調用 SqlSession selectList 方法

先看selectOne方法

  @Overridepublic <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.<T>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;}}

?

看到這句話沒

this.<T>selectList(statement, parameter);

我們調用返回一條數據的方法,實際上也是調用selectList,現在看selectList:多個重載方法我就不全部貼了

  @Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

?

MappedStatement你可以理解為你配置文件里面寫的sql語句的映射,比如:

同樣,這個對象也來自Configuration。接著看,return的是Executor的query方法,ms作為參數。Executor有多個實現類,BaseExecutor是最基礎的實現,來看其中的query實現:

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

?

第一行,從ms里面獲得綁定的sql語句,腦袋是不是跟配置產生一點聯系了?第二行不需要管,看第三行的實現:

  @SuppressWarnings("unchecked")@Overridepublic <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 (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601
      deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482
        clearLocalCache();}}return list;}

?

當查詢的時候先從緩存中找,如果找不到就從數據庫中找,這里我們看

list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}

?

?這句是關鍵:

list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

在BaseExecutor里面有定義

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;

?

它是需要子類去實現。總共有三種:BatchExecutor,ReuseExecutor,SimpleExecutor。這里我們選擇SimpleExecutor:

  @Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}

?

?這里說一句題外話:到處都需要Configuration,它無疑是mybatis運行期間的核心。

StatementHandler是對java.sql.Statement的封裝處理,有三個實現類:CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler。

同樣,利用Configuration來創建一個StatementHandler實例,之后利用這個handler來創建一個java.sql.statement,最后調用handler的<E>query方法,利用原生的Statement來執行查詢操作。

PreparedStatementHandler.query方法

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

?

?前兩行都應該知道,類型轉換以及執行jdbc的查詢。最后一行是利用原生的PreparedStatement來進行結果集的封裝。ResultSetHandler有個默認實現類:DefaultResultSetHandler,具體就不在分析了。

到此為止,返回結果集到最上層,顯示給用戶。

?

先寫這些吧,寫的不是特別滿意,望指教~

?

轉載于:https://www.cnblogs.com/LUA123/p/8094573.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/539756.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/539756.shtml
英文地址,請注明出處:http://en.pswp.cn/news/539756.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

提取多個字段_動態合并多個工作表,數據再多也不怕

小伙伴們好啊&#xff0c;今天老祝和大家分享一個動態合并多個工作表的技巧。很多時候&#xff0c;咱們的數據是按照部門或是月份等項目&#xff0c;分別存放在不同工作表中的&#xff0c;要對這些數據進行分析匯總的時候&#xff0c;需要先將不同工作表中的數據合并到一起才可…

深入理解Oracle的并行操作【好文認真讀】

請尊重原文作者&#xff0c;http://czmmiao.iteye.com/blog/1487568 -------------------------------------------------------------------------------- 并行&#xff08;Parallel&#xff09;和OLAP系統 并行的實現機制是&#xff1a;首先&#xff0c;Oracle會創建一個進…

服務器虛擬化性能瓶頸怎么辦,如何突破虛擬化三大瓶頸

如果你希望在應用虛擬化技術的過程中不出現任何問題的話&#xff0c;那么顯然這是不切合實際的期望。虛擬化技術能給你的數據中心帶來諸多好處&#xff0c;但是為了可以利用虛擬化的優勢&#xff0c;你需要了解可能會面對哪些問題。即使虛擬化技術已經被應用到了許多企業中&…

2017-2018-1 20155229 《信息安全系統設計基礎》第十四周學習總結

2017-2018-1 20155229 《信息安全系統設計基礎》第十四周學習總結 對“第三章 程序機器級表示”的深入學習 我選擇這章的理由是第一次學的時候還是不太理解&#xff0c;老師也有說這章建議在認真學習&#xff0c;所以本周的學習任務是認真再次學習這一章c語言、匯編代碼以及機器…

python中為什么推薦使用with_Python中的with關鍵字使用詳解

這篇文章主要介紹了Python 中的with關鍵字使用詳解的相關資料,在Python中,with關鍵字是一個替你管理實現上下文協議對象的好東西,需要的朋友可以參考下">在 Python 2.5 中&#xff0c; with 關鍵字被加入。它將常用的 try ... except ... finally ... 模式很方便的被復…

create table as select性能測試

轉載自&#xff1a;http://blog.csdn.net/yangzhijun_cau/article/details/7396088 --------------------------------------------------------------------------------- 原表270W數據&#xff0c;無照片&#xff0c;字段比較多&#xff0c;有50個左右 測試機是一個虛擬機&a…

類似索引Model套Model之 iOS模型閑聊二

看下界面, 這是類似于索引的頁面, 只不過木有右側索引條的布局. 如果想了解通訊錄索引的,請移步iOS - 高仿通訊錄之商品索引排序搜索. 提供思路如下: 分析界面及接口用 MVC 設計模式來實現(其實核心點都在下面5)創建內外層 Model 并綁定兩者 Model兩者 Cell 布局的實現 (便于后…

輸入法畫面_搜狗輸入法:用AI技術譜寫詩意生活

十九世紀著名的思想家斯賓塞曾說&#xff1a;科學本身就富有詩意。這里應該包含兩種意思&#xff0c;字面上&#xff0c;科學是飽含文字之美的&#xff0c;比如原理和規律的推演&#xff0c;僅通過文字符號的簡單排列&#xff0c;便有了生機。但深層次上科學又不止于文字&#…

hadoop偽分布式(單機版)安裝,Linux

一、下載 1、hadoop官網下載&#xff1a;https://archive.apache.org/dist/hadoop/common/ 進入stable文件夾里下載&#xff0c;這是穩定版本。 stable/ 本文的版本是 hadoop-2.7.2.tar.gz 2、jdk下載&#xff0c;JDK7及以上&#xff0c;本文用jdk8-64位 二、版本區別 2.…

c++-add two numbers 兩個鏈表相加

題目描述 You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. Input: (2 -> 4 -> 3) (5 -> 6…

城軌的兩類時鐘系統均同步于_基于兩臺SDS3000示波器同步產生“8通道”示波器...

在很多應用場合需要4通道以上的示波器&#xff0c;但是市面上極大部分示波器最多只有四通道&#xff0c;而且沒有外部輸入的同步時鐘接口。 有什么快捷的方法獲得更多通道功能的示波器&#xff1f; 最簡便的方法是:將兩臺示波器的輔助輸入信號作為觸發源&#xff0c;同時連接到…

Linux設置ssh免密碼登錄

一、SSH來源 對于需要遠程管理其它機器&#xff0c;一般使用遠程桌面或者telnet。linux一般只能是telnet。但是telnet的缺點是通信不加密&#xff0c;存在不安全因素&#xff0c;只適合內網訪問。 為解決這個問題&#xff0c;推出了通信加密通信協議&#xff0c;即SSH&#x…

解析json數據_Retrofit同時解析JSON和XML數據格式

前言Android開發中&#xff0c;我們會經常遇到前端需要解析兩種數據格式(json和xml),比如自己服務器返回的是json格式的數據&#xff0c;我們做微信登錄的時候&#xff0c;微信返回的格式又是xml格式的。我們可以通過自己編寫Retrofit的ConverterFactory來做到可以同時解析兩種…

ORACLE 小時值必須介于1和12之間 解決方法

ORACLE數據庫查詢語句&#xff1a; "select * from dual where time>to_date(2012-10-29 19:45:34,yyyy-mm-dd HH:mi:ss)"當執行時&#xff0c;會拋出錯誤&#xff1a;ORA-01849: 小時值必須介于 1 和 12 之間 01849. 00000 - "hour must be between 1 and 1…

jenkins+svn+maven+ssh 部署配置詳細記錄

2019獨角獸企業重金招聘Python工程師標準>>> 先簡單記錄一下&#xff0c;后面再慢慢完善。 1、環境 jdk 1.7.0_45 maven 3.1.1 jenkins 2.3.21 jdk和maven的安裝就不必多說了&#xff0c;主要是jenkins的安裝需要說下&#xff0c;jenkins有war包和yum還有rpm等安裝方…

k8s安裝sqlite3_kubernetes環境部署單節點redis數據庫的方法

kubernetes部署redis數據庫(單節點)redis簡介Redis 是我們常用的非關系型數據庫&#xff0c;在項目開發、測試、部署到生成環境時&#xff0c;經常需要部署一套 Redis 來對數據進行緩存。這里介紹下如何在 Kubernetes 環境中部署用于開發、測試的環境的 Redis 數據庫&#xff0…

oracle 都是parallel惹的禍【1-2分鐘出結果變1-2秒】

原文&#xff1a;http://blog.csdn.net/shushugood/article/details/9000628 -------------------------------------------------------- 該項目是中國聯通xxxx話務系統&#xff0c;我的架構設計需求設計&#xff0c;運維保障數據庫開發&#xff0c;全套服務。 在今天開發完畢…

二叉搜索樹(BST樹)的簡單實現

#include <stdlib.h>template<typename T>class CBinSTree;template <typename T>class CTreeNode{//樹節點類public:CTreeNode(const T& item,CTreeNode<T>* lptr NULL,CTreeNode<T>* rptr NULL):data(item),left(lptr),right(rptr){}CTr…

Oracle 創建 DBLink 的方法

原文出處&#xff1a;http://blog.csdn.net/davidhsing/article/details/6408770 ------------------- 1、如果需要創建全局 DBLink&#xff0c;則需要先確定用戶有創建 dblink 的權限&#xff1a; [c-sharp] view plaincopy print?select * from user_sys_privs where privi…

eclipse init 配置

--設置最大的堆和最小堆大小.兩者一樣表示固定大小.這樣可以防止老年代內存擴展造成額外的gc.當然也會多占一些內存.系統內存不足的慎用 -Xms512m -Xmx512m --加大年輕代內存.減少minor gc -Xmn164m --這個是永久代大小.默認是64M,增加到96M.固定大小,減少擴展造成的gc -XX:Per…