MyBatis系列之淺談SQL執行流程分析

獨立使用Mybatis

這篇文章主要以分析Mybatis框架執行SQL的流程。
回憶曾經獨立使用Mybatis半自動化框架時,我們需要執行以下步驟:

  1. 讀取配置文件(mybatis-config.xml),初始化配置類即configuration;
  2. 創建SQLSessionFactory;
  3. 創建SqlSession;
  4. 執行SQL,處理結果集
    對應如下代碼:
public class MyBatisUtils {
    private final static SqlSessionFactory SQL_SESSION_FACTORY;
    static {
        String resource = "mybatis-config.xml";
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(reader);
    }
    public static SqlSessionFactory getSqlSessionFactory() {
        return SQL_SESSION_FACTORY;
    }
}

//單元測試
public class MapperTest {
    static SqlSessionFactory sqlSessionFactory = null;
    static {
        sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();
    }

    @Test
    public void testAdd() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = new User();
            user.setUsername("hello");
            user.setAge(5);
            userMapper.insert(user);
            sqlSession.commit();    // 這裏一定要提交,不然數據進不去數據庫中
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void getUser() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.selectByPrimaryKey(32L);
            System.out.println("name: " + user.getUsername() + "| age: " + user.getAge());
        } finally {
        }
    }
}

Mybatis執行流程

基於上面說明,我們基本瞭解了執行流程,下面我們從源碼層面解釋一下流程。

SqlSessionFactory\SqlSession

在這裏插入圖片描述
獨立使用Mybatis時,第一步要讀取配置文件,因此,我們將從讀取配置文件開始。

  1. SqlSessionFactoryBuilder讀取mybatis的配置文件,調用build方法創建DefaultSqlSessionFactory
 /**通過XMLConfigBuilder解析mybatis配置,創建SQLSessionFactory對象*/
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //parse()方法XMLConfigBuilder類解析xml文件,同時完成configuration屬性的創建
            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.
            }
        }
    }

    /**
     * 以上build()方法默認都是調用此方法,創建DefaultSqlSessionFactory對象
     * 解析mybatis配置文件xml後,生成Configuration對象,然後生成SQLSessionFactory對象
     * @param config
     * @return
     */
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

  1. 獲取SqlsessionFactory之後,調用openSession()方法創建SQLSession,這裏說明一下SQLSession僅僅向外提供了對數據庫操作的支持,真正執行對數據庫的操作是execute的職責。
DefaultSqlSessionFactory類
/**
   * 通常一系列openSession方法都會調用該方法
   * @param execType
   * @param level
   * @param autoCommit
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //通過Confuguration對象去獲取Mybatis相關配置信息, Environment對象包含了數據源和事務的配置
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //之前說了,從表面上來看,咱們是用sqlSession在執行sql語句, 實際呢,其實是通過excutor執行, excutor是對於Statement的封裝
      final Executor executor = configuration.newExecutor(tx, execType);
      //創建SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      // may have fetched a connection so lets call close()
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

獲取SQLSession之後,對數據進行CRUD操作的準備工作就正式結束。

MapperProxy

在這裏插入圖片描述
主要處理我們Mybatis的映射文件。該類主要負責代理開發中的mapper。那麼思考一下該代理對象如何獲取呢?
下面我們從SQLSession中跟蹤。

DefaultSQLSession類中
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }


/**
     * 獲取Mapper接口的代理對象
     */
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //從緩存中獲取該Mapper接口的代理工廠對象
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        //如果該Mapper接口沒有註冊過,則拋異常
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            //**使用代理工廠創建Mapper接口的代理對象**重點部分
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

MapperProxyFactory類
 /**
     * 創建mapperInterface的代理對象
     */
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

一路跟蹤下來,我們發現MapperProxy對象是在MapperProxyFactory裏創建完成。

Excutor

在這裏插入圖片描述
上面我們提到Excutor的職責是負責對數據的crud操作,上面的時序圖,詳細地說明了SQL的執行過程。
對於每一個MapperProxy對應開發人員自定的Mapper(dao)接口,下面我們將從源碼追蹤,如何實現的。

  • MappProxy:
 /**代理對象執行的方法,代理以後,所有Mapper的方法調用時,都會調用這個invoke方法*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //判斷是否爲通用類,mybatis使用JDK代理方式,即面向接口,false
            //method.getDeclaringClass()返回底層的類對象的class
            if (Object.class.equals(method.getDeclaringClass())) {
                //如果是類,則利用反射機制,返回目標對象
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                //如果是默認方法,則執行默認方法,java1.8提供;
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        //從緩存中獲取MapperMethod對象,如果緩存中沒有,則創建一個,並添加到緩存中
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //執行方法對應的SQL語句,返回查詢的結果
        /**
         * 1、先判斷SQL的方法類型,insert、select、update
         * 2、根據參數列表的值(value),轉成參數名稱和參數值對應關係的map對象
         * 3、根據方法名和參數列表的對應關係,查詢出結果列表
         */
        return mapperMethod.execute(sqlSession, args);
    }
  • MapperMethod:
/**
     * 核心方法,用於執行對應的SQL語句
     * @param sqlSession
     * @param args
     * @return
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                //param 爲參數名和參數值的對應關係,即map集合
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    //void類型且方法中有ResultHandler參數(特殊參數),調用sqlSession.select執行
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    //返回類型是集合或者數組,調用sqlSession.<E>selectList執行
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    //返回 map ,調用 sqlSession.<K, V>selectMap
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                            (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                    + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }

本篇文章主要是淺析SQL的執行流程,因此我們建議以selectList爲例,帶領大家熟悉一下流程,後面會專門介紹
各個步驟的流程。
迴歸到SQLSession

public <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();
    }
  }
  • BaseExecutor:
/**
     * 查詢方法,專門提供select執行的方法
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //獲取查詢SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        //創建緩存的key,即作爲HashMap中的key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //執行查詢
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

  • SimpleExecutor:
@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);
    }
  }
  • preparedStatement:
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

參考資料:Spring源碼深度解析第二版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章