Mybatis原始碼分析-整體設計(一)

fondtiger發表於2021-09-09

SSM是目前常見的構建Web專案的方案,Mybatis是其中重要的一環,如果能深刻的理解Mybatis的內部原理,對我們會有極大的幫助,接下來一起看看Mybatis的內部設計。

準備

  1. 搭建Mybatis的基本執行環境,參考

  2. 貼上自己的程式碼
public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

            // 查詢資料庫內容
            sqlSession = sqlSessionFactory.openSession();
            User user = sqlSession.selectOne("me.aihe.dao.UserMapper.selectUser",1);
            System.out.println(user);

            // 插入資料庫內容

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

分析

  1. 獲取Mybatis的配置檔案,內部透過ClassLoader載入檔案流,這一步需要對Classloader有一定的理解,裡面相對簡單,就不多說了
    Resources.getResourceAsStream("mybatis.xml");
  2. 建立SqlSessionFactory, 透過JDK內部的w3c解析配置檔案的內容,封裝到Configration物件中,最後透過Configuration來建立DefaultSqlSessionFactory.
  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);
  }
  1. 透過SqlSessionFactory建立SqlSession物件

圖片描述

  • 獲取配置的env引數,在解析配置的時候根據配置生成了TransactionFactory
  • TransactionFactory物件,在設定env後,預設是JDBCTransactionFactory
  • 透過配置生成預設的executor,executor是很重要的元件,可以看到executor封裝了很多運算元據庫相關的東西
  • 生成預設的DefaultSqlSession,內部有executor,TransactionFactory,configuration

圖片描述

 @Override
  public 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();
    }
  }
  1. SqlSession透過key獲取到與key繫結的sql語句,並且執行,最後獲取到結果。這步內部可以細分
sqlSession.selectOne("me.aihe.dao.UserMapper.selectUser",1);

selectOne內部呼叫的是selectList的函式,selectList函式內部,首先獲取到key對應的MappedStatement,然後透過Executor查詢MapperStatement。

在解析配置的時候建立key與MappedStatement對映關係的

@Override
  public  List 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();
    }
  }
  1. 不同的executor內部的查詢方法不同,後面我們詳細講解不同的executor。這次先看SimpleExecutor.
  • queryStack判斷當前的SQL執行棧,可能會連續執行多條sql語句
  • localCache 快取當前Sqlsession查詢到的物件
  • queryFromDatabase 如果沒有快取從資料庫中進行查詢,重點語句
  @Override
  public  List 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);
 }

 @SuppressWarnings("unchecked")
  @Override
  public  List 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 list;
    try {
      queryStack++;
      list = resultHandler == null ? (List) 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;
  }
  1. query方法中真正的查詢交給子類的doQuery,而SimpleExecutor的doQuery如下
  • 新建StatementHandler,語句處理器
  • 透過StatementHandler再進行query
  • Statement為JDK提供的SQL介面,獲取Statement經歷了StatementHandler.prepare,然後parameterize設定引數。
  • query方法利用MySQL內部的query
  • 獲取到結果之後,透過resultHandler處理獲取到的結果
@Override
  public  List 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);
    }
  }
  1. StatementHandler內部的呼叫,結果集處理使用的是DefaultResultSetHandler,內部ResultSet的處理屬於JDBC的知識,想看懂Mybatis,對JDBC也要有一定的理解

圖片描述

  1. 獲取到結果返回給我們
    User user = sqlSession.selectOne("me.aihe.dao.UserMapper.selectUser",1);

回顧

這次大致看了下Mybatis的基本執行流程,涉及到了幾個關鍵的類

  • SqlSessionFactory ,SqlSession
  • MappedStatement 封裝我們寫的SQL語句
  • StatementHandler 語句處理器
  • ResultHandler 返回的結果處理器
  • Executor Mybatis中的語句執行器

建立SqlSessionFactory -> 獲取SqlSession -> 獲取->MappedStatement -> 獲取StatementHandler同時建立Statement -> 執行Statement -> 使用ResultSet處理執行的結果,處理結果根據Mapperr.xml檔案中指定的型別對映最終實現ORM功能

總結

Mybatis的過程相比Spring MVC更直觀一些,不過需要熟悉JDBC的知識,內部還有一些細節,後續繼續研究

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2508/viewspace-2812745/,如需轉載,請註明出處,否則將追究法律責任。

相關文章