mybatis源碼分析(二):mybatis在執(zhí)行SQL語句之前都做了什么

SqlSessionFactory構(gòu)建過程

在上一遍我們通過JDK的動態(tài)代理簡單實現(xiàn)了一個“mybatis框架”(mybatis源碼分析(一):自己動手寫一個簡單的mybaits框架)券盅,并分析了一下我們自己的框架還有那些問題需要解決避咆,帶著這些問題我們就可以去看一下mybatis的源碼。mybatis作為一個持久層框架是如何解決這些問題的静秆。

學習一個框架的基本步驟就是找到框架的源碼的入口牵素,然后打斷點一步一步看內(nèi)部的實現(xiàn)細節(jié)严衬。看官方的文檔mybatis的入口大概是這樣的:

每個基于 MyBatis 的應(yīng)用都是以一個 SqlSessionFactory 的實例為核心的笆呆。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得请琳。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預(yù)先配置的 Configuration 實例來構(gòu)建出 SqlSessionFactory 實例。 既然有了 SqlSessionFactory赠幕,顧名思義俄精,我們可以從中獲得 SqlSession 的實例。SqlSession 提供了在數(shù)據(jù)庫執(zhí)行 SQL 命令所需的所有方法榕堰。你可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句竖慧。

也就是說嫌套,使用mybatis首先需要構(gòu)建出SqlSessionFactory,然后通過SqlSessionFactory獲得SqlSession實例圾旨,再通過SqlSession就可以操作數(shù)據(jù)庫了踱讨。

所以mybatis的入口是SqlSession,那么我們首先需要了解SqlSessionFactory是怎么構(gòu)建的砍的,SqlSession又是怎么被創(chuàng)建出來的痹筛,我們通過代碼逐步分析一下。

構(gòu)建SqlSessionFactory代碼:

public SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resource = "mybatis/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //1廓鞠、拿到全局配置
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        //準備環(huán)境信息
        Environment environment = new Environment("development", transactionFactory, dataSource);
        //2帚稠、使用我們的數(shù)據(jù)源
        sqlSessionFactory.getConfiguration().setEnvironment(environment);
        //使用mybatis-config.xml + Spring的數(shù)據(jù)源
        return sqlSessionFactory;
    }

我這里是用mybatis配置文件構(gòu)建的SqlSessionFactory,當然也可以用其他方法床佳。接下來我們看一下SqlSessionFactory在構(gòu)建的過程中都做了些什么滋早。

核心是這一行代碼:

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

mybatis的源碼:

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.
      }
    }
  }

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

SqlSessionFactory子類DefaultSqlSessionFactory的構(gòu)造方法:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

.....
}

我們可以看到SqlSessionFactory構(gòu)造方法非常簡單,創(chuàng)建一個DefaultSqlSessionFactory夕土,而DefaultSqlSessionFactory實例在實例化的時候需要創(chuàng)建了一個Configuration實例馆衔,而Configuration實例是通過XMLConfigBuilder實例的parse()方法得到的。

那么重點就是Configuration怨绣,我們來看一下SqlSessionFactory的Configuration是個什么東西。

public class Configuration {

  protected Environment environment;


  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
  .....

  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;

  protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  ......
}

我們可以看到Configuration里面全部都是mybatis框架本身的所有的配置以及各種組件的定義拷获,從Configuration中我們可以得到很多的信息篮撑,有“cacheEnabled”緩存的配置,有“l(fā)azyLoadingEnabled”懶加載的配置匆瓜,有“InterceptorChain”攔截器的配置等等等赢笨。

那么可以推測出mybatis會把所有的配置項提前構(gòu)建好,Configuration里面有什么mybatis這個框架就會支持什么驮吱。我們可以再順便看一下源碼里Configuration里面的配置項是如何賦值的茧妒,這些配置項的來源是什么。這樣我們就可以熟練的配置mybatis了左冬。

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 {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(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);
    }
  }

看到這里就有一種很熟悉的感覺了桐筏,XNode就是XML的節(jié)點對象,而“mappers”拇砰,"properties"梅忌,"settings"這些東西都是mybatis官方給的xml配置項,mybatis會解析我們配置的mybatis.xml除破,然后通過xml的配置給Configuration對象賦值牧氮。

還有XMLConfigBuilder構(gòu)造方法里面有一個super(newConfiguration())調(diào)用父類的構(gòu)造方法。

  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;
  }

構(gòu)造方法會去賦值typeAliasRegistry和typeHandlerRegistry這兩個屬性瑰枫。

 public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

typeAliasRegistry和typeHandlerRegistry這兩個屬性的值是在Configuration里面寫死的踱葛。


  public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
    ......
  }

 public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());
    ......

  }

可以總結(jié)一下,Configuration的配置項來源于xml配置和mybatis自己定義的部分,這些配置在SqlSessionFactory構(gòu)建的時候就會全部初始化好尸诽。
例如我們的mybatis-config.xml是這樣的:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <mappers>
        <mapper resource="mapper/DistrictMapper.xml"/>
    </mappers>

</configuration>

就會得到如下的賦值:


image.png

image.png

同時mybatis還會加載我們在mapper.xml里定義的sql方法甥材,并把他封裝在了mappedStatements和loadedResources里了。

SqlSession構(gòu)建過程

那么構(gòu)建SqlSessionFactory的流程就大概整明白了逊谋,我們接下來看一下SqlSession是怎么被創(chuàng)建出來的擂达。
先寫一段測試代碼:

    @Test
    public void testMyBatis() throws IOException {
        //1、得到 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //2胶滋、得到 sqlSession ,代表和數(shù)據(jù)庫一次回話
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3板鬓、得到真正操作數(shù)據(jù)庫的Dao
        DistrictMapper mapper = sqlSession.getMapper(DistrictMapper.class);
        District district = mapper.getById(1);
        System.out.println(district);
        sqlSession.close();
    }

先看這一句:

  SqlSession sqlSession = sqlSessionFactory.openSession();

mybaits的源碼是這樣的:

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  @Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }

  @Override
  public SqlSession openSession(TransactionIsolationLevel level) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
  }

  @Override
  public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
    return openSessionFromDataSource(execType, level, 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();
    }
  }

可以看到openSession是一個重載的方法,它們都會通過openSessionFromDataSource去創(chuàng)建openSession究恤,而openSessionFromDataSource接受三個參數(shù):ExecutorType(執(zhí)行器類型)俭令,TransactionIsolationLevel(數(shù)據(jù)庫隔離級別),autoCommit(是否自動提交)部宿。

用戶可以通過重載的方法創(chuàng)建不同的SqlSession抄腔,我們使用的是無參的openSession方法,ExecutorType會被賦值成默認的ExecutorType.SIMPLE類型理张。

再接著看赫蛇,openSessionFromDataSource這三段代碼。

      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

我們通過前面配置好的configuration拿到environment雾叭,又通過environment 拿到我們已經(jīng)創(chuàng)建好的TransactionFactory(事務(wù)工廠)悟耘,再通過事務(wù)工廠拿到Transaction對象。

通過源碼可以看到這里也是僅僅創(chuàng)建了一個對象织狐,并沒有其他操作暂幼。

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }

然后重點是這個代碼:

  final Executor executor = configuration.newExecutor(tx, execType);

Executor執(zhí)行器,這個名字很核心然后我們看一下Executor是個什么東西移迫。

根據(jù)參數(shù)可以看到openSession方法給我們創(chuàng)建了一個SimpleExecutor執(zhí)行器旺嬉,除此之外我們還可以創(chuàng)建BatchExecutor、ReuseExecutor厨埋、CachingExecutor邪媳。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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 (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

然后可以看到SimpleExecutor就是真正去執(zhí)行Sql語句的組件,并且繼承BaseExecutor揽咕。而BaseExecutor幾乎實現(xiàn)了操作數(shù)據(jù)庫的所有方法悲酷。

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  public <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.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  ......

}

看到這里openSession的所有操作就看完了∏咨疲可以總結(jié)一下:

1.openSession通過之前構(gòu)建好的Configuration實例拿到了Transaction對象
2.通過transaction對象和ExecutorType類型給我們創(chuàng)建好了一個數(shù)據(jù)庫的執(zhí)行器Executor设易,默認的Executor是SimpleExecutor
3.通過Executor創(chuàng)建了一個DefaultSqlSession實例給我們

可以畫個時序圖出來:


image.png

我們可以看到,到這一步mybatis都沒開始連接數(shù)據(jù)庫蛹头。而是幫我們把一切東西都準備好顿肺,其中最核心的東西就是SimpleExecutor戏溺,接下來應(yīng)該就是我們操作數(shù)據(jù)庫的主角SimpleExecutor要登場了。

下面一篇文章我將帶著大家一起學習mybatis核心組件Executor的執(zhí)行過程屠尊,希望可以給你帶來收獲旷祸。

本文涉及的源碼放在github上:

https://github.com/burgleaf/mybatis-study

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市讼昆,隨后出現(xiàn)的幾起案子托享,更是在濱河造成了極大的恐慌,老刑警劉巖浸赫,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闰围,死亡現(xiàn)場離奇詭異,居然都是意外死亡既峡,警方通過查閱死者的電腦和手機羡榴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來运敢,“玉大人校仑,你說我怎么就攤上這事〈荩” “怎么了迄沫?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卦方。 經(jīng)常有香客問我邢滑,道長,這世上最難降的妖魔是什么愿汰? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮乐纸,結(jié)果婚禮上衬廷,老公的妹妹穿的比我還像新娘。我一直安慰自己汽绢,他們只是感情好吗跋,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宁昭,像睡著了一般跌宛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上积仗,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天疆拘,我揣著相機與錄音,去河邊找鬼寂曹。 笑死哎迄,一個胖子當著我的面吹牛回右,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播漱挚,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼翔烁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旨涝?” 一聲冷哼從身側(cè)響起蹬屹,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎白华,沒想到半個月后慨默,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡衬鱼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年业筏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸟赫。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒜胖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抛蚤,到底是詐尸還是另有隱情台谢,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布岁经,位于F島的核電站朋沮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缀壤。R本人自食惡果不足惜樊拓,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望塘慕。 院中可真熱鬧筋夏,春花似錦、人聲如沸图呢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛤织。三九已至赴叹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間指蚜,已是汗流浹背乞巧。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姚炕,地道東北人摊欠。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓丢烘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親些椒。 傳聞我的和親對象是個殘疾皇子播瞳,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容