Mybatis源碼概覽(一)

      通常拿到源碼會無從下手,個人基本思路通常就是根據一個基本的helloWorld Debug下去,把主線先大概理一遍,而後再具體分析細節,沒有必要一個類一個類細看,看了也會忘掉。本身理源碼的時候看不下去時,能夠結合網上的分析文章,一邊看別人的解析,一邊本身對照源碼。瞭解框架設計原理,之後項目中出了問題能夠更容易定位。再往上一層面,之後本身能夠根據需求擴展框架。java

先執行個HelloWorldmysql

   去github上 clone Mybatis代碼,而後再其測試源碼裏添加以下代碼
git

示例代碼,裏面未貼出來的類自行補全。github

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession(true);

        try {
            //後面介紹mybatis經過動態代理來避免手工調用session,直接調用dao接口;
            //BlogDao mapper = session.getMapper(BlogDao.class);
            //List<Blog> blogs= mapper.selectAll();
            List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");
            System.out.println(blogs);

        } finally {
            session.close();
        }

  mybatis-config.xmlspring

<?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>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/BlogMapper.xml"/>
    </mappers>
</configuration>

  BlogMapper.xml
sql

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.robin.BlogDao">
    <resultMap type="org.apache.ibatis.robin.Blog" id="blog">
        <result property="id"        column="id"/>
        <result property="context"     column="context"/>
        <result property="dateCreate"  column="date_create"/>
    </resultMap>
    <insert id="insert" parameterType="org.apache.ibatis.robin.Blog">
            insert into blog(id,context,date_create)
            values (#{id},#{context},now())
    </insert>

    <select id="selectAll" resultMap="blog" flushCache="true" >
        SELECT * from blog
    </select>
</mapper>

注意xml文件放到項目的resource位置,能夠經過ide來設置,不然程序會獲取不到。 數據庫


一步一步DEBUG

    解析配置,構建SqlSessionFactory

    從上面的代碼能夠看到mybatis重要的執行順序:輸入配置流,由SqlSessionFacotryBuilder來根據輸入的配置構建出來一個SqlSessionFactory。apache

//簡略代碼 
//解析配置
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//根據配置返回SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

   

    構建打開SqlSession (executor生成,plugin織入,緩存開閉)

    獲取到SqlSessionFactory後,下一步確定是得到sqlSession。緩存

sqlSessionFactory.openSession(true);
//這裏的true 表示是否自動commit


//debug 進去 這部分是openSession的大致過程;
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //先經過configuration獲取咱們再xml中配的environment,裏面包含事務和DataSource;
    final Environment environment = configuration.getEnvironment();
    //這一步是獲取JDBC事務
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    
    //打開Executor過程,見下段落的分析
    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();
  }
}


   打開Executor的過程,Executor默認的類型是SIMPLEsession

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);
  }
  //若是在配置文件settings中打開 <setting name="cacheEnabled" value="true" /> 
  // cacheEnabled 着爲true,開啓全局緩存,也就是二級緩存;
  //下面查詢具體分析CachingExecutor執行查詢過程
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  //若是自定義了Plugin攔截器,在xml經過plugins配置後,這一步會經過JDK動態代理織入到executor中,
  //生成一個帶了攔截器方法功能的Executor
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}


在根BaseExecutor中能夠看到 ,Executor不只包含了事務,還同時加入localCache ,也就是Session級別的緩存,也是你們常叫的一級緩存,這裏提一下一級緩存是默認的,若是非要去掉只能經過在select 語句配置中 flushCache="true"。

protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
  this.localCache = new PerpetualCache("LocalCache");
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
}

  Executor組裝好以後經過new DefaultSqlSession返回咱們的SqlSession;

new DefaultSqlSession(configuration, executor, autoCommit);
  
    執行具體查詢(重要概念MappedStatement)

SqlSession是直接對數據庫發號施令的組件。經過發起下面一個SQL查詢,繼續Debug進去

List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");

  先mybatis自動補全一些默認參數(rowBounds主要是指代返回的行數限制)後,進去下面的代碼

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    //MappedStatement是Mybatis的精髓,若是Bean對Spring是同樣的道理;這裏咱們要重點介紹下
    //屬性MappedStatement 是一個SQL執行語句的包裝,裏面的屬性有SqlSource(指代SQL具體的語句);
    //屬性StatementType 若是是PREPARED代表執行預編譯執行,這樣不只防止SQL注入,並且還能避免SQL重複解析;
    //屬性id 指代咱們惟一肯定MappedStatement惟一標識;
    //還有ResultMap 和ParameterMap等等,這裏就不解釋來;
    //能夠說MappedStatement是發起SQL請求的所需的必備數據;
    MappedStatement ms = configuration.getMappedStatement(statement);
    //進行query查詢,轉下段分析
    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();
  }
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //獲取SQL
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //經過參數,sql,和rowBounds一塊兒拼裝出cacheKey
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//進入到查詢
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  //先判斷是否有緩存中去取
  Cache cache = ms.getCache();
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  //緩存中取不到,直接執行query,這裏delegate指代咱們前面生成的SimpleExecutor;
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//跳轉到下面的方法
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 (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  //清掉cache
  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;
}
//從這個方法能夠明顯看出裏面的localCache,指代前面說的Session緩存,即一級緩存
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 {
    //這一步主要是獲取Connection,而後準備PreparedState,執行查詢返回結果
    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;
}

至此咱們的查詢SQL就執行完了。

這一路涉及了 Configuration --->SqlSessionFactory------>SqlSession(裏面又包裝了 Executor,Cache,MappedStatement) 這幾個重要概念;

下節主要講Mybatis經過代理 把原先本身須要直接調用 sqlSession來執行 改爲只需調用相應dao接口類,在實際項目中省掉大量代碼,以及與spring結合的實現原理;


本文連接 http://my.oschina.net/robinyao/blog/645263

相關文章
相關標籤/搜索