mybatis源碼解析-如何執行查詢操作

//1.通過輸入流解析xml配置文件
InputStream inputstream = Resources.getResourceAsStream("xxx.xml")
SqlsessionFactory sqlsessionfactory = new SqlsessionFactoryBuilder().build(inputstream);
//2.獲取和數據庫的鏈接,創建會話
SqlSession openSession = sqlsessionfactory.openSession();
//3.獲取接口的實現類對象,會爲接口自動的創建一個代理對象mapper,代理對象會去執行增刪改查方法
xxxMapper mapper = openSession.getMapper(xxxMapper.class)
//4.執行增刪改的方法

mybatis源碼解析-SqlsessionFactory
mybatis源碼解析-獲取Sqlsession
mybatis源碼解析-getMapper
一個mybatis運行前的步驟我們都分析的差不多了,現在來看一下具體的執行過程

查詢如何實現

我們假如有一個方法叫做getXXXById(),我們便順理成章的進入了這個方法內部,我們之前提到過,openSession.getMapper(xxxMapper.class)返回的是MapperProxy對象,也就是mapper的代理對象,我們看下代碼:
1.1

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

首先這個invoke方法會先做判斷:if (Object.class.equals(method.getDeclaringClass()))它的意思是判斷通過代理對象執行的方法是不是Object方法,或者是不是mapper接口中的方法,如果是Object的方法比如toString就直接執行下面的代碼了。入如果是mapper接口中的方法,則會將當前方法通過MapperMethod mapperMethod = this.cachedMapperMethod(method);封裝成MapperMethod,然後通過mapperMethod.execute(this.sqlSession, args);來執行,這個參數中的sqlsession是我們MapperProxy封裝好了的,Object[] args是我們查詢方法的參數,我們來看一下MapperMethod這個類:
1.2

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

這是這個類的基本信息,其中MapperMethod.SqlCommand command是這個類中的靜態內部類,在構造方法中就進行了解析,目的是判斷要運行的方法是增刪改查的那種類型,然後看這個類中的execute方法:

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;

我們要運行的是根據id查詢對象,所以是select類型的,進入了select之後會根據返回值類型進行判斷,我們的情況都不滿足,直接進入else語句,這裏面的method.convertArgsToSqlCommandParam(args)是將Object類型的參數轉爲我們需要的類型,我們不妨細看一下:
在這裏插入圖片描述
看到這裏是不是很熟悉,和mybatis參數處理過程及原理裏面講得一樣,這裏就不多說了,參數轉換完成後,就會執行sqlSession.selectOne(this.command.getName(), param);這個方法,其中參數this.command.getName() 就是我們在mapper.xml中寫的select標籤的唯一標識,即方法所在接口的全限定類名,我們進入這個selectOne方法
1.3這個方法是DefaultSqlSession實現的

public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.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;
        }
    }

我們發現,無論是查詢單個還是多個,都調用了selectList方法,我們來看一看這個方法,參數statement是我們對應mapper接口中的一個方法,有唯一標識,parameter是方法的參數
1.4

public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }

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

        return var5;
    }

這裏需要注意了,MappedStatement ms = this.configuration.getMappedStatement(statement); 就是根據configuration全局配置對象來獲取之前被封裝的MappedStatement對象,而這個對象正是封裝了增刪改查標籤中的信息,包括我們配置的SQL,SQL的id,緩存信息,resultMap等,每個標籤都有一個該對象封裝,這個在本票文章開始的mybatis源碼解析-SqlsessionFactory有詳細介紹,每個增刪改查標籤都有一個封裝它的MappedStatement對象,他們通過map封裝,這裏的selectList方法通過唯一標識statement找到了對應的接口方法
我們接着往下走,
完成了MappedStatement的查詢,會將這個作爲參數傳遞給this.executor.query方法,我們來看一下這個方法:其中有個參數this.wrapCollection(parameter),我們看一下這個封裝參數的方法:

private Object wrapCollection(Object object) {
        DefaultSqlSession.StrictMap map;
        if (object instanceof Collection) {
            map = new DefaultSqlSession.StrictMap();
            map.put("collection", object);
            if (object instanceof List) {
                map.put("list", object);
            }

            return map;
        } else if (object != null && object.getClass().isArray()) {
            map = new DefaultSqlSession.StrictMap();
            map.put("array", object);
            return map;
        } else {
            return object;
        }
    }

是不是有一種豁然開朗的感覺?是的,就是這段代碼的作用我們纔可以將List類型的集合封裝爲list或者是collection,好了,現在跳過這段代碼,接着往下走,看query方法,注意了,這裏的this.executor.query是我們在獲取SqlSession步驟中創建Executor時的Executor子類CachingExecutor
1.5

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

注意第一句的BoundSql,我們進入ms.getBoundSql看一下:
在這裏插入圖片描述
這個類提供三個主要的屬性:sql,parameterMappings,parameterObject,我們這裏簡單介紹一下:

  • sql就是我們卸載增刪改標籤裏的sql語句
  • parameterObject就是我們寫在接口裏方法的參數
  • parameterMappings,它是一個List,它的每一個參數都是ParameterMapping對象,這個對象會描述我們的參數,包括屬性,名稱,表達式,javatype等重要信息,我們一般不需要去改變它,通過它可以實現參數和SQL的結合

回到query方法,裏面還有一個二級緩存key:CacheKey key,這裏就不多說它了,然後它再次調用本類中的另一個重載的query方法:
1.6

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) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

在之前介紹獲取sqlsession的文章中我們提到,通過openSession創建的Executor也就是executor = new SimpleExecutor(this, transaction);這段代碼被executor = new CachingExecutor((Executor)executor);封裝了,真正起作用的是CachingExecutor類中的delegate,如果沒有緩存,這個執行器進行查詢this.delegate.query,我們進入這個query方法:

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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

我們發現會執行**list = resultHandler == null ? (List)this.localCache.getObject(key) : null;**這行代碼,這是執行了二級緩存之後再執行的本地緩存,這也應證了mybatis會先查二級,再查一級緩存,如果一級二級都爲null,就會執行queryFromDatabase方法,我們進入這個方法:
1.7

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }

        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

這個方法會現在本地緩存中設置一個key,再查出數據放進本地緩存,我們看doQuery方法,這個方法的參數我們都很熟悉了,直接進入該方法了
1.8

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

這個方法就很有意思了,裏面有Statement原生JDBC,而且出現了StatementHandler,這是SqlSession下的一個重要的對象,我們之前說的Executor也是一個,它的作用是創建出statement(prepareStatement)對象,起到承上啓下的作用,我們進入這個方法看一下:
1.9

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

我們在mapper中寫select標籤時有一個屬性:
在這裏插入圖片描述
默認採取預編譯模式,那麼在這個方法中就會創建PreparedStatementHandler它是StatementHandler的子類
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

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