Mybatis源碼分析——Select語句的執(zhí)行過程分析(上)

前言

上一篇我們分析了Mapper接口代理類的生成,本篇接著分析是如何調(diào)用到XML中的SQL

我們回顧一下MapperMethod 的execute方法

public class MapperMethod {
    
    //包含SQL相關(guān)信息,比喻MappedStatement的id屬性,(mapper.UserMapper.getAll)
    private final SqlCommand command;

    //包含了關(guān)于執(zhí)行的Mapper方法的參數(shù)類型和返回類型。
    private final MethodSignature method;

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        
        // 根據(jù) SQL 類型執(zhí)行相應(yīng)的數(shù)據(jù)庫操作
        switch (command.getType()) {
            case INSERT: {
                // 對用戶傳入的參數(shù)進(jìn)行轉(zhuǎn)換,下同
                Object param = method.convertArgsToSqlCommandParam(args);
                // 執(zhí)行插入操作,rowCountResult 方法用于處理返回值
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                 // 執(zhí)行更新操作
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                // 執(zhí)行刪除操作
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // 根據(jù)目標(biāo)方法的返回類型進(jìn)行相應(yīng)的查詢操作
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    // 執(zhí)行查詢操作,并返回多個(gè)結(jié)果 
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    // 執(zhí)行查詢操作,并將結(jié)果封裝在 Map 中返回
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    // 執(zhí)行查詢操作,并返回一個(gè) Cursor 對象
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 執(zhí)行查詢操作,并返回一個(gè)結(jié)果
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            case FLUSH:
                // 執(zhí)行刷新操作
                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;
    }
}

selectOne 方法分析

本節(jié)選擇分析 selectOne 方法,主要是因?yàn)?selectOne 在內(nèi)部會(huì)調(diào)用 selectList 方法。同時(shí)分析 selectOne 方法等同于分析 selectList 方法。代碼如下

result = sqlSession.selectOne(command.getName(), param);

我們看到是通過sqlSession來執(zhí)行查詢的,并且傳入的參數(shù)為command.getName()和param,也就是namespace.methodName(mapper.UserMapper.getAll)和方法的運(yùn)行參數(shù)。我們知道了,所有的數(shù)據(jù)庫操作都是交給sqlSession來執(zhí)行的,那我們就來看看sqlSession的方法

public class DefaultSqlSession implements SqlSession {

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        // 調(diào)用 selectList 獲取結(jié)果
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
            // 返回結(jié)果
            return list.get(0);
        } else if (list.size() > 1) {
            // 如果查詢結(jié)果大于1則拋出異常
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
}

如上,selectOne 方法在內(nèi)部調(diào)用 selectList 了方法,并取 selectList 返回值的第1個(gè)元素作為自己的返回值。如果 selectList 返回的列表元素大于1,則拋出異常。下面我們來看看 selectList 方法的實(shí)現(xiàn)。

DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;
    private final Executor executor;

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        // 調(diào)用重載方法
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 通過MappedStatement的Id獲取 MappedStatement
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 調(diào)用 Executor 實(shí)現(xiàn)類中的 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();
        }
    }
}

我們之前創(chuàng)建DefaultSqlSession的時(shí)候,是創(chuàng)建了一個(gè)Executor的實(shí)例作為其屬性的,我們看到通過MappedStatement的Id獲取 MappedStatement后,就交由Executor去執(zhí)行了

我們回顧一下前面的文章,Executor的創(chuàng)建過程,代碼如下

public class Configuration {

    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

    public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
    }

    //創(chuàng)建一個(gè)執(zhí)行器,默認(rèn)是SIMPLE
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        //根據(jù)executorType來創(chuàng)建相應(yīng)的執(zhí)行器,Configuration默認(rèn)是SIMPLE
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            //創(chuàng)建SimpleExecutor實(shí)例,并且包含Configuration和transaction屬性
            executor = new SimpleExecutor(this, transaction);
        }
        
        //如果要求緩存,生成另一種CachingExecutor,裝飾者模式,默認(rèn)都是返回CachingExecutor
        /**
         * 二級緩存開關(guān)配置示例
         * <settings>
         *   <setting name="cacheEnabled" value="true"/>
         * </settings>
         */
        if (cacheEnabled) {
            //CachingExecutor使用裝飾器模式,將executor的功能添加上了二級緩存的功能,二級緩存會(huì)單獨(dú)文章來講
            executor = new CachingExecutor(executor);
        }
        
        //此處調(diào)用插件,通過插件可以改變Executor行為,此處我們后面單獨(dú)文章講
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
}

executor包含了Configuration和Transaction,默認(rèn)的執(zhí)行器為SimpleExecutor,如果開啟了二級緩存(默認(rèn)開啟),則CachingExecutor會(huì)包裝SimpleExecutor,那么我們該看CachingExecutor的query方法了

public class CachingExecutor implements Executor {

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 獲取 BoundSql
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 創(chuàng)建 CacheKey
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        // 調(diào)用重載方法
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

上面的代碼用于獲取 BoundSql 對象,創(chuàng)建 CacheKey 對象,然后再將這兩個(gè)對象傳給重載方法。CacheKey 以及接下來即將出現(xiàn)的一二級緩存將會(huì)獨(dú)立成文進(jìn)行分析。

獲取 BoundSql

// 獲取 BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);

調(diào)用了MappedStatement的getBoundSql方法,并將運(yùn)行時(shí)參數(shù)傳入其中,我們大概的猜一下,這里是不是拼接SQL語句呢,并將運(yùn)行時(shí)參數(shù)設(shè)置到SQL語句中?

我們都知道 SQL 是配置在映射文件中的,但由于映射文件中的 SQL 可能會(huì)包含占位符 #{},以及動(dòng)態(tài) SQL 標(biāo)簽,比如 <if>、<where> 等。因此,我們并不能直接使用映射文件中配置的 SQL。MyBatis 會(huì)將映射文件中的 SQL 解析成一組 SQL 片段。我們需要對這一組片段進(jìn)行解析,從每個(gè)片段對象中獲取相應(yīng)的內(nèi)容。然后將這些內(nèi)容組合起來即可得到一個(gè)完成的 SQL 語句,這個(gè)完整的 SQL 以及其他的一些信息最終會(huì)存儲(chǔ)在 BoundSql 對象中。下面我們來看一下 BoundSql 類的成員變量信息,如下:

public class BoundSql {

    private final String sql;
    private final List<ParameterMapping> parameterMappings;
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;
}

下面用一個(gè)表格列舉各個(gè)成員變量的含義。

變量名 類型 用途
sql String 一個(gè)完整的 SQL 語句,可能會(huì)包含問號 ? 占位符
parameterMappings List 參數(shù)映射列表,SQL 中的每個(gè) #{xxx} 占位符都會(huì)被解析成相應(yīng)的 ParameterMapping 對象
parameterObject Object 運(yùn)行時(shí)參數(shù),即用戶傳入的參數(shù),比如 Article 對象,或是其他的參數(shù)
additionalParameters Map 附加參數(shù)集合,用于存儲(chǔ)一些額外的信息,比如 datebaseId 等
metaParameters MetaObject additionalParameters 的元信息對象

接下來我們接著MappedStatement 的 getBoundSql 方法,代碼如下:

public final class MappedStatement {

    public BoundSql getBoundSql(Object parameterObject) {
    
        // 調(diào)用 sqlSource 的 getBoundSql 獲取 BoundSql,把method運(yùn)行時(shí)參數(shù)傳進(jìn)去
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }

        // check for nested result maps in parameter mappings (issue #30)
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            String rmId = pm.getResultMapId();
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }

        return boundSql;
    }
}

MappedStatement 的 getBoundSql 在內(nèi)部調(diào)用了 SqlSource 實(shí)現(xiàn)類的 getBoundSql 方法,并把method運(yùn)行時(shí)參數(shù)傳進(jìn)去,SqlSource 是一個(gè)接口,它有如下幾個(gè)實(shí)現(xiàn)類:

  • DynamicSqlSource
  • RawSqlSource
  • StaticSqlSource
  • ProviderSqlSource
  • VelocitySqlSource

當(dāng) SQL 配置中包含 ${}(不是 #{})占位符,或者包含 <if>、<where> 等標(biāo)簽時(shí),會(huì)被認(rèn)為是動(dòng)態(tài) SQL,此時(shí)使用 DynamicSqlSource 存儲(chǔ) SQL 片段。否則,使用 RawSqlSource 存儲(chǔ) SQL 配置信息。我們來看看DynamicSqlSource的getBoundSql

DynamicSqlSource
public class DynamicSqlSource implements SqlSource {

    private final Configuration configuration;
    private final SqlNode rootSqlNode;

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        // 創(chuàng)建 DynamicContext
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        
        // 解析 SQL 片段,并將解析結(jié)果存儲(chǔ)到 DynamicContext 中,
        // 這里會(huì)將${}替換成method對應(yīng)的運(yùn)行時(shí)參數(shù),也會(huì)解析<if><where>等SqlNode
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        
        /*
         * 構(gòu)建 StaticSqlSource,在此過程中將 sql 語句中的占位符 #{} 替換為問號 ?,
         * 并為每個(gè)占位符構(gòu)建相應(yīng)的 ParameterMapping
         */
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        
        // 調(diào)用 StaticSqlSource 的 getBoundSql 獲取 BoundSql
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        
        // 將 DynamicContext 的 ContextMap 中的內(nèi)容拷貝到 BoundSql 中
        for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
            boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
        }
        return boundSql;
    }
}

該方法由數(shù)個(gè)步驟組成,這里總結(jié)一下:

  • 1、創(chuàng)建 DynamicContext
  • 2、解析 SQL 片段,并將解析結(jié)果存儲(chǔ)到 DynamicContext 中
  • 3、解析 SQL 語句,并構(gòu)建 StaticSqlSource
  • 4、調(diào)用 StaticSqlSource 的 getBoundSql 獲取 BoundSql
  • 5、將 DynamicContext 的 ContextMap 中的內(nèi)容拷貝到 BoundSql
DynamicContext

DynamicContext 是 SQL 語句構(gòu)建的上下文,每個(gè) SQL 片段解析完成后,都會(huì)將解析結(jié)果存入 DynamicContext 中。待所有的 SQL 片段解析完畢后,一條完整的 SQL 語句就會(huì)出現(xiàn)在 DynamicContext 對象中。

public class DynamicContext {

    public static final String PARAMETER_OBJECT_KEY = "_parameter";
    public static final String DATABASE_ID_KEY = "_databaseId";

    //bindings 則用于存儲(chǔ)一些額外的信息,比如運(yùn)行時(shí)參數(shù)
    private final ContextMap bindings;
    
    //sqlBuilder 變量用于存放 SQL 片段的解析結(jié)果
    private final StringBuilder sqlBuilder = new StringBuilder();

    public DynamicContext(Configuration configuration, Object parameterObject) {
        // 創(chuàng)建 ContextMap,并將運(yùn)行時(shí)參數(shù)放入ContextMap中
        if (parameterObject != null && !(parameterObject instanceof Map)) {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            bindings = new ContextMap(metaObject);
        } else {
            bindings = new ContextMap(null);
        }
        
        // 存放運(yùn)行時(shí)參數(shù) parameterObject 以及 databaseId
        bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
        bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
    }

    public void bind(String name, Object value) {
        bindings.put(name, value);
    }

    //拼接Sql片段
    public void appendSql(String sql) {
        sqlBuilder.append(sql);
        sqlBuilder.append(" ");
    }

    //得到sql字符串
    public String getSql() {
        return sqlBuilder.toString().trim();
    }

    //繼承HashMap
    static class ContextMap extends HashMap<String, Object> {

        private MetaObject parameterMetaObject;
        
        public ContextMap(MetaObject parameterMetaObject) {
            this.parameterMetaObject = parameterMetaObject;
        }

        @Override
        public Object get(Object key) {
            String strKey = (String) key;
            // 檢查是否包含 strKey,若包含則直接返回
            if (super.containsKey(strKey)) {
                return super.get(strKey);
            }

            if (parameterMetaObject != null) {
                // issue #61 do not modify the context when reading
                // 從運(yùn)行時(shí)參數(shù)中查找結(jié)果,這里會(huì)在${name}解析時(shí),通過name獲取運(yùn)行時(shí)參數(shù)值,替換掉${name}字符串
                return parameterMetaObject.getValue(strKey);
            }

            return null;
        }
    }
    
    // 省略部分代碼

}  
解析 SQL 片段

接著我們來看看解析SQL片段的邏輯

rootSqlNode.apply(context);

對于一個(gè)包含了 ${} 占位符,或 <if>、<where> 等標(biāo)簽的 SQL,在解析的過程中,會(huì)被分解成多個(gè)片段。每個(gè)片段都有對應(yīng)的類型,每種類型的片段都有不同的解析邏輯。在源碼中,片段這個(gè)概念等價(jià)于 sql 節(jié)點(diǎn),即 SqlNode。

StaticTextSqlNode 用于存儲(chǔ)靜態(tài)文本,TextSqlNode 用于存儲(chǔ)帶有 ${} 占位符的文本,IfSqlNode 則用于存儲(chǔ) <if> 節(jié)點(diǎn)的內(nèi)容。MixedSqlNode 內(nèi)部維護(hù)了一個(gè) SqlNode 集合,用于存儲(chǔ)各種各樣的 SqlNode。接下來,我將會(huì)對 MixedSqlNode 、StaticTextSqlNode、TextSqlNode、IfSqlNode、WhereSqlNode 以及 TrimSqlNode 等進(jìn)行分析

public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 遍歷 SqlNode 集合
        for (SqlNode sqlNode : contents) {
            // 調(diào)用 salNode 對象本身的 apply 方法解析 sql
            sqlNode.apply(context);
        }
        return true;
    }
}

MixedSqlNode 可以看做是 SqlNode 實(shí)現(xiàn)類對象的容器,凡是實(shí)現(xiàn)了 SqlNode 接口的類都可以存儲(chǔ)到 MixedSqlNode 中,包括它自己。MixedSqlNode 解析方法 apply 邏輯比較簡單,即遍歷 SqlNode 集合,并調(diào)用其他 SqlNode實(shí)現(xiàn)類對象的 apply 方法解析 sql。

StaticTextSqlNode
public class StaticTextSqlNode implements SqlNode {
    private final String text;

    public StaticTextSqlNode(String text) {
        this.text = text;
    }

    @Override
    public boolean apply(DynamicContext context) {
        //直接拼接當(dāng)前sql片段的文本到DynamicContext的sqlBuilder中
        context.appendSql(text);
        return true;
    }

}

StaticTextSqlNode 用于存儲(chǔ)靜態(tài)文本,直接將其存儲(chǔ)的 SQL 的文本值拼接到 DynamicContext 的sqlBuilder中即可。下面分析一下 TextSqlNode。

TextSqlNode
public class TextSqlNode implements SqlNode {
    private final String text;
    private final Pattern injectionFilter;

    @Override
    public boolean apply(DynamicContext context) {
        // 創(chuàng)建 ${} 占位符解析器
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        
        // 解析 ${} 占位符,通過ONGL 從用戶傳入的參數(shù)中獲取結(jié)果,替換text中的${} 占位符
        // 并將解析結(jié)果的文本拼接到DynamicContext的sqlBuilder中
        context.appendSql(parser.parse(text));
        return true;
    }

    private GenericTokenParser createParser(TokenHandler handler) {
        // 創(chuàng)建占位符解析器
        return new GenericTokenParser("${", "}", handler);
    }

    private static class BindingTokenParser implements TokenHandler {

        private DynamicContext context;
        private Pattern injectionFilter;

        public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
            this.context = context;
            this.injectionFilter = injectionFilter;
        }

        @Override
        public String handleToken(String content) {
            Object parameter = context.getBindings().get("_parameter");
            if (parameter == null) {
                context.getBindings().put("value", null);
            } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
                context.getBindings().put("value", parameter);
            }
            
            // 通過 ONGL 從用戶傳入的參數(shù)中獲取結(jié)果
            Object value = OgnlCache.getValue(content, context.getBindings());
            String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
            
            // 通過正則表達(dá)式檢測 srtValue 有效性
            checkInjection(srtValue);
            return srtValue;
        }
    }
}   

GenericTokenParser 是一個(gè)通用的標(biāo)記解析器,用于解析形如 {name},#{id} 等標(biāo)記。此時(shí)是解析{name}的形式,從運(yùn)行時(shí)參數(shù)的Map中獲取到key為name的值,直接用運(yùn)行時(shí)參數(shù)替換掉 ${name}字符串,將替換后的text字符串拼接到DynamicContext的sqlBuilder中

舉個(gè)例子吧,比喻我們有如下SQL

SELECT * FROM user WHERE name = '${name}' and id= ${id}

假如我們傳的參數(shù) Map中name值為 chenhao,id為1,那么該 SQL 最終會(huì)被解析成如下的結(jié)果:

SELECT * FROM user WHERE name = 'chenhao'; DROP TABLE user;#'

由于傳入的參數(shù)沒有經(jīng)過轉(zhuǎn)義,最終導(dǎo)致了一條 SQL 被惡意參數(shù)拼接成了兩條 SQL。這就是為什么我們不應(yīng)該在 SQL 語句中是用 ${} 占位符,風(fēng)險(xiǎn)太大。接著我們來看看IfSqlNode

IfSqlNode
public class IfSqlNode implements SqlNode {
    private final ExpressionEvaluator evaluator;
    private final String test;
    private final SqlNode contents;

    public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
    }

    @Override
    public boolean apply(DynamicContext context) {
        // 通過 ONGL 評估 test 表達(dá)式的結(jié)果
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            // 若 test 表達(dá)式中的條件成立,則調(diào)用其子節(jié)點(diǎn)節(jié)點(diǎn)的 apply 方法進(jìn)行解析
            // 如果是靜態(tài)SQL節(jié)點(diǎn),則會(huì)直接拼接到DynamicContext中
            contents.apply(context);
            return true;
        }
        return false;
    }

}

IfSqlNode 對應(yīng)的是 <if test='xxx'> 節(jié)點(diǎn),首先是通過 ONGL 檢測 test 表達(dá)式是否為 true,如果為 true,則調(diào)用其子節(jié)點(diǎn)的 apply 方法繼續(xù)進(jìn)行解析。如果子節(jié)點(diǎn)是靜態(tài)SQL節(jié)點(diǎn),則子節(jié)點(diǎn)的文本值會(huì)直接拼接到DynamicContext中

好了,其他的SqlNode我就不一一分析了,大家有興趣的可以去看看

解析 #{} 占位符

經(jīng)過前面的解析,我們已經(jīng)能從 DynamicContext 獲取到完整的 SQL 語句了。但這并不意味著解析過程就結(jié)束了,因?yàn)楫?dāng)前的 SQL 語句中還有一種占位符沒有處理,即 #{}。與 ${} 占位符的處理方式不同,MyBatis 并不會(huì)直接將 #{} 占位符替換為相應(yīng)的參數(shù)值,而是將其替換成?。其解析是在如下代碼中實(shí)現(xiàn)的

SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

我們看到將前面解析過的sql字符串和運(yùn)行時(shí)參數(shù)的Map作為參數(shù),我們來看看parse方法

public class SqlSourceBuilder extends BaseBuilder {

    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        // 創(chuàng)建 #{} 占位符處理器
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        
        // 創(chuàng)建 #{} 占位符解析器
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        
        // 解析 #{} 占位符,并返回解析結(jié)果字符串
        String sql = parser.parse(originalSql);
        
        // 封裝解析結(jié)果到 StaticSqlSource 中,并返回,因?yàn)樗械膭?dòng)態(tài)參數(shù)都已經(jīng)解析了,可以封裝成一個(gè)靜態(tài)的SqlSource
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }
    
    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

        private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
        private Class<?> parameterType;
        private MetaObject metaParameters;

        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
            super(configuration);
            this.parameterType = parameterType;
            this.metaParameters = configuration.newMetaObject(additionalParameters);
        }

        public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
        }

        @Override
        public String handleToken(String content) {
            // 獲取 content 的對應(yīng)的 ParameterMapping
            parameterMappings.add(buildParameterMapping(content));
            // 返回 ?
            return "?";
        }
    }
}

我們看到將Sql中的 #{} 占位符替換成"?",并且將對應(yīng)的參數(shù)轉(zhuǎn)化成ParameterMapping 對象,通過buildParameterMapping 完成,最后創(chuàng)建一個(gè)StaticSqlSource,將sql字符串和ParameterMappings為參數(shù)傳入,返回這個(gè)StaticSqlSource

public class SqlSourceBuilder extends BaseBuilder {
    
    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

        private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
        private Class<?> parameterType;
        private MetaObject metaParameters;


        /*
         * 將#{xxx} 占位符中的內(nèi)容解析成 Map。
         *   #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
         *      上面占位符中的內(nèi)容最終會(huì)被解析成如下的結(jié)果:
         *  {
         *      "property": "age",
         *      "typeHandler": "MyTypeHandler", 
         *      "jdbcType": "NUMERIC", 
         *      "javaType": "int"
         *  }
         */
        private ParameterMapping buildParameterMapping(String content) {
            Map<String, String> propertiesMap = parseParameterMapping(content);
            String property = propertiesMap.get("property");
            Class<?> propertyType;
            
            // metaParameters 為 DynamicContext 成員變量 bindings 的元信息對象
            if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
                propertyType = metaParameters.getGetterType(property);
                
            /*
             * parameterType 是運(yùn)行時(shí)參數(shù)的類型。如果用戶傳入的是單個(gè)參數(shù),比如 Employe 對象,此時(shí) 
             * parameterType 為 Employe.class。如果用戶傳入的多個(gè)參數(shù),比如 [id = 1, author = "chenhao"],
             * MyBatis 會(huì)使用 ParamMap 封裝這些參數(shù),此時(shí) parameterType 為 ParamMap.class。
             */
            } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
                propertyType = parameterType;
            } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
                propertyType = java.sql.ResultSet.class;
            } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
                propertyType = Object.class;
            } else {
            
            /*
             * 代碼邏輯走到此分支中,表明 parameterType 是一個(gè)自定義的類,
             * 比如 Employe,此時(shí)為該類創(chuàng)建一個(gè)元信息對象
             */
            MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
            // 檢測參數(shù)對象有沒有與 property 想對應(yīng)的 getter 方法
            if (metaClass.hasGetter(property)) {
                // 獲取成員變量的類型
                propertyType = metaClass.getGetterType(property);
            } else {
                propertyType = Object.class;
            }
            }
            ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
            
            // 將 propertyType 賦值給 javaType
            Class<?> javaType = propertyType;
            String typeHandlerAlias = null;
            
            // 遍歷 propertiesMap
            for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                if ("javaType".equals(name)) {
                    // 如果用戶明確配置了 javaType,則以用戶的配置為準(zhǔn)
                    javaType = resolveClass(value);
                    builder.javaType(javaType);
                } else if ("jdbcType".equals(name)) {
                    // 解析 jdbcType
                    builder.jdbcType(resolveJdbcType(value));
                } else if ("mode".equals(name)) {
                    builder.mode(resolveParameterMode(value));
                } else if ("numericScale".equals(name)) {
                    builder.numericScale(Integer.valueOf(value));
                } else if ("resultMap".equals(name)) {
                    builder.resultMapId(value);
                } else if ("typeHandler".equals(name)) {
                    typeHandlerAlias = value;
                } else if ("jdbcTypeName".equals(name)) {
                    builder.jdbcTypeName(value);
                } else if ("property".equals(name)) {
                    // Do Nothing
                } else if ("expression".equals(name)) {
                    throw new BuilderException("Expression based parameters are not supported yet");
                } else {
                    throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
                }
            }
            if (typeHandlerAlias != null) {
                builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
            }
            
            // 構(gòu)建 ParameterMapping 對象
            return builder.build();
        }
    }
}

SQL 中的 #{name, ...} 占位符被替換成了問號 ?。#{name, ...} 也被解析成了一個(gè) ParameterMapping 對象。我們再來看一下 StaticSqlSource 的創(chuàng)建過程。如下:

public class StaticSqlSource implements SqlSource {

    private final String sql;
    private final List<ParameterMapping> parameterMappings;
    private final Configuration configuration;

    public StaticSqlSource(Configuration configuration, String sql) {
        this(configuration, sql, null);
    }

    public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.configuration = configuration;
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        // 創(chuàng)建 BoundSql 對象
        return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }

}

最后我們通過創(chuàng)建的StaticSqlSource就可以獲取BoundSql對象了,并傳入運(yùn)行時(shí)參數(shù)

BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

也就是調(diào)用上面創(chuàng)建的StaticSqlSource 中的getBoundSql方法,這是簡單的 return new BoundSql(configuration, sql, parameterMappings, parameterObject); ,接著看看BoundSql

public class BoundSql {

    private final String sql;
    private final List<ParameterMapping> parameterMappings;
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;

    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<String, Object>();
        this.metaParameters = configuration.newMetaObject(additionalParameters);
    }

    public String getSql() {
        return sql;
    }
    
    //略......
}

我們看到只是做簡單的賦值。BoundSql中包含了sql,#{}解析成的parameterMappings,還有運(yùn)行時(shí)參數(shù)parameterObject。好了,SQL解析我們就介紹這么多。我們先回顧一下我們代碼是從哪里開始的

CachingExecutor
public class CachingExecutor implements Executor {

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 獲取 BoundSql
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        
        // 創(chuàng)建 CacheKey
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        
        // 調(diào)用重載方法
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

如上,我們剛才都是分析的第三行代碼,獲取到了BoundSql,CacheKey 和二級緩存有關(guān),我們留在下一篇文章單獨(dú)來講,接著我們看第七行重載方法 query

public class CachingExecutor implements Executor {

    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
        // 從 MappedStatement 中獲取緩存
        Cache cache = ms.getCache();
        
        // 若映射文件中未配置緩存或參照緩存,此時(shí) cache = null
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 若緩存未命中,則調(diào)用被裝飾類的 query 方法,也就是SimpleExecutor的query方法
                    list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        
        // 調(diào)用被裝飾類的 query 方法,這里的delegate我們知道應(yīng)該是SimpleExecutor
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

上面的代碼涉及到了二級緩存,若二級緩存為空,或未命中,則調(diào)用被裝飾類的 query 方法。被裝飾類為SimpleExecutor,而SimpleExecutor繼承BaseExecutor,那我們來看看 BaseExecutor 的query方法

BaseExecutor.query
public abstract class BaseExecutor implements Executor {

    @Override
    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.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            queryStack++;
            // 從一級緩存中獲取緩存項(xiàng),一級緩存我們也下一篇文章單獨(dú)講
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 一級緩存未命中,則從數(shù)據(jù)庫中查詢
                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;
    }
}

從一級緩存中查找查詢結(jié)果。若緩存未命中,再向數(shù)據(jù)庫進(jìn)行查詢。至此我們明白了一級二級緩存的大概思路,先從二級緩存中查找,若未命中二級緩存,再從一級緩存中查找,若未命中一級緩存,再從數(shù)據(jù)庫查詢數(shù)據(jù),那我們來看看是怎么從數(shù)據(jù)庫查詢的

BaseExecutor.queryFromDatabase
public abstract class BaseExecutor implements Executor {

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 向緩存中存儲(chǔ)一個(gè)占位符
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 調(diào)用 doQuery 進(jìn)行查詢
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 移除占位符
            localCache.removeObject(key);
        }
        // 緩存查詢結(jié)果
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

}

調(diào)用了doQuery方法進(jìn)行查詢,最后將查詢結(jié)果放入一級緩存,我們來看看doQuery,在SimpleExecutor中

SimpleExecutor
public abstract class BaseExecutor implements Executor {

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

}


public class SimpleExecutor extends BaseExecutor {

    @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();
            // 創(chuàng)建 StatementHandler
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 創(chuàng)建 Statement
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 執(zhí)行查詢操作
            return handler.<E>query(stmt, resultHandler);
        } finally {
            // 關(guān)閉 Statement
            closeStatement(stmt);
        }
    }
}

我們先來看看第一步創(chuàng)建StatementHandler

創(chuàng)建StatementHandler

StatementHandler有什么作用呢?通過這個(gè)對象獲取Statement對象,然后填充運(yùn)行時(shí)參數(shù),最后調(diào)用query完成查詢。我們來看看其創(chuàng)建過程

public class Configuration {

    protected final InterceptorChain interceptorChain = new InterceptorChain();

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 創(chuàng)建具有路由功能的 StatementHandler
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 應(yīng)用插件到 StatementHandler 上
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
}

我們看看RoutingStatementHandler的構(gòu)造方法

public class RoutingStatementHandler implements StatementHandler {

    private final StatementHandler delegate;

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

        // 根據(jù) StatementType 創(chuàng)建不同的 StatementHandler 
        switch (ms.getStatementType()) {
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }

    }
}

RoutingStatementHandler 的構(gòu)造方法會(huì)根據(jù) MappedStatement 中的 statementType 變量創(chuàng)建不同的 StatementHandler 實(shí)現(xiàn)類。那statementType 是什么呢?我們還要回顧一下MappedStatement 的創(chuàng)建過程

public final class MappedStatement {

    public static class Builder {
        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
            mappedStatement.configuration = configuration;
            mappedStatement.id = id;
            mappedStatement.sqlSource = sqlSource;
            
            mappedStatement.statementType = StatementType.PREPARED;
            mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
            mappedStatement.resultMaps = new ArrayList<ResultMap>();
            mappedStatement.sqlCommandType = sqlCommandType;
            mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            String logId = id;
            if (configuration.getLogPrefix() != null) {
                logId = configuration.getLogPrefix() + id;
            }
            mappedStatement.statementLog = LogFactory.getLog(logId);
            mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
        }
    }
}

我們看到statementType 的默認(rèn)類型為PREPARED,這里將會(huì)創(chuàng)建PreparedStatementHandler。

接著我們看下面一行代碼prepareStatement,

創(chuàng)建 Statement

創(chuàng)建 Statement 在 stmt = prepareStatement(handler, ms.getStatementLog());這句代碼,那我們跟進(jìn)去看看

public class SimpleExecutor extends BaseExecutor {

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 獲取數(shù)據(jù)庫連接
        Connection connection = getConnection(statementLog);
        // 創(chuàng)建 Statement,
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 為 Statement 設(shè)置參數(shù)
        handler.parameterize(stmt);
        return stmt;
    }
}

在上面的代碼中我們終于看到了和jdbc相關(guān)的內(nèi)容了,創(chuàng)建完Statement,最后就可以執(zhí)行查詢操作了。由于篇幅的原因,我們留在下一篇文章再來詳細(xì)講解

參考:
https://www.cnblogs.com/java-chen-hao/p/11754184.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 久違的晴天,家長會(huì)。 家長大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,825評論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽閱讀 10,871評論 0 11
  • 可愛進(jìn)取,孤獨(dú)成精。努力飛翔,天堂翱翔。戰(zhàn)爭美好,孤獨(dú)進(jìn)取。膽大飛翔,成就輝煌。努力進(jìn)取,遙望,和諧家園??蓯塾巫?..
    趙原野閱讀 3,509評論 1 1
  • 在妖界我有個(gè)名頭叫胡百曉,無論是何事,只要找到胡百曉即可有解決的辦法。因?yàn)槭侵缓偞蠹乙杂瀭饔灲形摇皟A城百曉”,...
    貓九0110閱讀 3,715評論 7 3

友情鏈接更多精彩內(nèi)容