hi,你好!欢迎访问本站!登录
本站由网站地图腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

Mybaits 源码剖析 (六)----- Select 语句的实行历程剖析(上篇)

2019-11-18杂谈搜奇网38°c
A+ A-

上一篇我们剖析了Mapper接口代办类的生成,本篇接着剖析是怎样挪用到XML中的SQL

我们回忆一下MapperMethod 的execute要领

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    
    // 依据 SQL 范例实行响应的数据库操纵
    switch (command.getType()) {
        case INSERT: {
            // 对用户传入的参数举行转换,下同
            Object param = method.convertArgsToSqlCommandParam(args);
            // 实行插进去操纵,rowCountResult 要领用于处置惩罚返回值
            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()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                // 实行查询操纵,并返回多个效果 
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                // 实行查询操纵,并将效果封装在 Map 中返回
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                // 实行查询操纵,并返回一个 Cursor 对象
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);
                // 实行查询操纵,并返回一个效果
                result = sqlSession.selectOne(command.getName(), param);
            }
            break;
        case FLUSH:
            // 实行革新操纵
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    return result;
}

selectOne 要领剖析

本节挑选剖析 selectOne 要领,重要是由于 selectOne 在内部会挪用 selectList 要领。同时剖析 selectOne 要领等同于剖析 selectList 要领。代码以下

// 实行查询操纵,并返回一个效果
result = sqlSession.selectOne(command.getName(), param);

我们看到是经由历程sqlSession来实行查询的,而且传入的参数为command.getName()和param,也就是namespace.methodName(mapper.EmployeeMapper.getAll)和要领的运转参数。我们晓得了,一切的数据库操纵都是交给sqlSession来实行的,那我们就来看看sqlSession的要领

DefaultSqlSession

public <T> T selectOne(String statement, Object parameter) {
    // 挪用 selectList 猎取效果
    List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) {
        // 返回效果
        return list.get(0);
    } else if (list.size() > 1) {
        // 如果查询效果大于1则抛出非常
        throw new TooManyResultsException(
            "Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}

如上,selectOne 要领在内部挪用 selectList 了要领,并取 selectList 返回值的第1个元素作为自身的返回值。如果 selectList 返回的列表元素大于1,则抛出非常。下面我们来看看 selectList 要领的完成。

DefaultSqlSession

private final Executor executor; 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) {
    try {
        // 经由历程MappedStatement的Id猎取 MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement); // 挪用 Executor 完成类中的 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();
    }
}

我们之前建立DefaultSqlSession的时刻,是建立了一个Executor的实例作为其属性的,我们看到经由历程MappedStatement的Id猎取 MappedStatement后,就交由Executor去实行了

我们回忆一下前面的文章,Executor的建立历程,代码以下

//建立一个实行器,默许是SIMPLE
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //依据executorType来建立响应的实行器,Configuration默许是SIMPLE
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      //建立SimpleExecutor实例,而且包含Configuration和transaction属性
      executor = new SimpleExecutor(this, transaction);
    }
    
    //如果请求缓存,生成另一种CachingExecutor,装潢者情势,默许都是返回CachingExecutor
    /**
     * 二级缓存开关设置示例
     * <settings>
     *   <setting name="cacheEnabled" value="true"/>
     * </settings>
     */
    if (cacheEnabled) {
      //CachingExecutor运用装潢器情势,将executor的功用增加上了二级缓存的功用,二级缓存会零丁文章来说
      executor = new CachingExecutor(executor);
    }
    //此处挪用插件,经由历程插件能够转变Executor行动,此处我们背面零丁文章讲
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

executor包含了Configuration和Transaction,默许的实行器为SimpleExecutor,如果开启了二级缓存(默许开启),则CachingExecutor会包装SimpleExecutor,那末我们该看CachingExecutor的query要领了

CachingExecutor

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 猎取 BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject); // 建立 CacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); // 挪用重载要领
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

上面的代码用于猎取 BoundSql 对象,建立 CacheKey 对象,然后再将这两个对象传给重载要领。CacheKey 以及接下来行将涌现的一二级缓存将会自力成文举行剖析。

猎取 BoundSql

我们先来看看猎取BoundSql

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

挪用了MappedStatement的getBoundSql要领,并将运转时参数传入个中,我们也许的猜一下,这里是不是是拼接SQL语句呢,并将运转时参数设置到SQL语句中?

我们都晓得 SQL 是设置在映照文件中的,但由于映照文件中的 SQL 可能会包含占位符 #{},以及动态 SQL 标签,比方 <if>、<where> 等。因而,我们并不能直接运用映照文件中设置的 SQL。MyBatis 会将映照文件中的 SQL 剖析成一组 SQL 片断。我们需要对这一组片断举行剖析,从每一个片断对象中猎取响应的内容。然后将这些内容组合起来即可获得一个完成的 SQL 语句,这个完全的 SQL 以及其他的一些信息终究会存储在 BoundSql 对象中。下面我们来看一下 BoundSql 类的成员变量信息,以下:

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

下面用一个表格枚举各个成员变量的寄义。

变量名 范例 用处
sql String 一个完全的 SQL 语句,可能会包含问号 ? 占位符
parameterMappings List 参数映照列表,SQL 中的每一个 #{xxx} 占位符都会被剖析成响应的 ParameterMapping 对象
parameterObject Object 运转时参数,即用户传入的参数,比方 Article 对象,或是其他的参数
additionalParameters Map 附加参数鸠合,用于存储一些分外的信息,比方 datebaseId 等
metaParameters MetaObject additionalParameters 的元信息对象

接下来我们接着MappedStatement 的 getBoundSql 要领,代码以下:

public BoundSql getBoundSql(Object parameterObject) {

    // 挪用 sqlSource 的 getBoundSql 猎取 BoundSql,把method运转时参数传进去
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);return boundSql;
}

MappedStatement 的 getBoundSql 在内部挪用了 SqlSource 完成类的 getBoundSql 要领,并把method运转时参数传进去,SqlSource 是一个接口,它有以下几个完成类:

  • DynamicSqlSource
  • RawSqlSource
  • StaticSqlSource
  • ProviderSqlSource
  • VelocitySqlSource

当 SQL 设置中包含 ${}(不是 #{})占位符,或许包含 <if>、<where> 等标签时,会被认为是动态 SQL,此时运用 DynamicSqlSource 存储 SQL 片断。不然,运用 RawSqlSource 存储 SQL 设置信息。我们来看看DynamicSqlSource的getBoundSql

DynamicSqlSource

public BoundSql getBoundSql(Object parameterObject) {
    // 建立 DynamicContext
    DynamicContext context = new DynamicContext(configuration, parameterObject);

    // 剖析 SQL 片断,并将剖析效果存储到 DynamicContext 中,这里会将${}替代成method对应的运转时参数,也会剖析<if><where>等SqlNode
 rootSqlNode.apply(context);
    
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    /*
     * 构建 StaticSqlSource,在此历程当中将 sql 语句中的占位符 #{} 替代为问号 ?,
     * 并为每一个占位符构建响应的 ParameterMapping */ SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); // 挪用 StaticSqlSource 的 getBoundSql 猎取 BoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject); // 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql 中
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
        boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
}

该要领由数个步骤构成,这里总结一下:

  1. 建立 DynamicContext
  2. 剖析 SQL 片断,并将剖析效果存储到 DynamicContext 中
  3. 剖析 SQL 语句,并构建 StaticSqlSource
  4. 挪用 StaticSqlSource 的 getBoundSql 猎取 BoundSql
  5. 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql

DynamicContext

DynamicContext 是 SQL 语句构建的上下文,每一个 SQL 片断剖析完成后,都会将剖析效果存入 DynamicContext 中。待一切的 SQL 片断剖析终了后,一条完全的 SQL 语句就会涌现在 DynamicContext 对象中。

public class DynamicContext {

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

    //bindings 则用于存储一些分外的信息,比方运转时参数
    private final ContextMap bindings; //sqlBuilder 变量用于寄存 SQL 片断的剖析效果
    private final StringBuilder sqlBuilder = new StringBuilder(); public DynamicContext(Configuration configuration, Object parameterObject) {
        // 建立 ContextMap,并将运转时参数放入ContextMap中
        if (parameterObject != null && !(parameterObject instanceof Map)) {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            bindings = new ContextMap(metaObject);
        } else {
            bindings = new ContextMap(null);
        }

        // 寄存运转时参数 parameterObject 以及 databaseId
        bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
        bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
    }

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

    //拼接Sql片断
    public void appendSql(String sql) {
        this.sqlBuilder.append(sql); this.sqlBuilder.append(" ");
    }
    
    //获得sql字符串
    public String getSql() {
        return this.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) {
                // 从运转时参数中查找效果,这里会在${name}剖析时,经由历程name猎取运转时参数值,替代掉${name}字符串
                return parameterMetaObject.getValue(strKey);
            }

            return null;
        }
    }
    // 省略部份代码
}

剖析 SQL 片断

接着我们来看看剖析SQL片断的逻辑

rootSqlNode.apply(context);

关于一个包含了 ${} 占位符,或 <if>、<where> 等标签的 SQL,在剖析的历程当中,会被分解成多个片断。每一个片断都有对应的范例,每种范例的片断都有差别的剖析逻辑。在源码中,片断这个观点等价于 sql 节点,即 SqlNode。

StaticTextSqlNode 用于存储静态文本,TextSqlNode 用于存储带有 ${} 占位符的文本,IfSqlNode 则用于存储 <if> 节点的内容。MixedSqlNode 内部保护了一个 SqlNode 鸠合,用于存储林林总总的 SqlNode。接下来,我将会对 MixedSqlNode 、StaticTextSqlNode、TextSqlNode、IfSqlNode、WhereSqlNode 以及 TrimSqlNode 等举行剖析

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) {
            // 挪用 salNode 对象自身的 apply 要领剖析 sql
            sqlNode.apply(context);
        }
        return true;
    }
}

MixedSqlNode 能够看作是 SqlNode 完成类对象的容器,通常完成了 SqlNode 接口的类都能够存储到 MixedSqlNode 中,包含它自身。MixedSqlNode 剖析要领 apply 逻辑比较简朴,即遍历 SqlNode 鸠合,并挪用其他 SqlNode完成类对象的 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) {
        //直接拼接当前sql片断的文本到DynamicContext的sqlBuilder中
 context.appendSql(text); return true;
    }
}

StaticTextSqlNode 用于存储静态文本,直接将其存储的 SQL 的文本值拼接到 DynamicContext 的sqlBuilder中即可。下面剖析一下 TextSqlNode。

TextSqlNode

public class TextSqlNode implements SqlNode {

    private final String text;
    private final Pattern injectionFilter;

    @Override
    public boolean apply(DynamicContext context) {
        // 建立 ${} 占位符剖析器
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        // 剖析 ${} 占位符,经由历程ONGL 从用户传入的参数中猎取效果,替代text中的${} 占位符 // 并将剖析效果的文本拼接到DynamicContext的sqlBuilder中
 context.appendSql(parser.parse(text)); return true;
    }

    private GenericTokenParser createParser(TokenHandler handler) {
        // 建立占位符剖析器
        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 从用户传入的参数中猎取效果
            Object value = OgnlCache.getValue(content, context.getBindings());
            String srtValue = (value == null ? "" : String.valueOf(value));
            // 经由历程正则表达式检测 srtValue 有效性
            checkInjection(srtValue);
            return srtValue;
        }
    }
}

GenericTokenParser 是一个通用的标记剖析器,用于剖析形如 ${name},#{id} 等标记。此时是剖析 ${name}的情势,从运转时参数的Map中猎取到key为name的值,直接用运转时参数替代掉 ${name}字符串,将替代后的text字符串拼接到DynamicContext的sqlBuilder中

举个例子吧,比方我们有以下SQL

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

如果我们传的参数 Map中name值为 chenhao,id为1,那末该 SQL 终究会被剖析成以下的效果:

SELECT * FROM user WHERE name = 'chenhao' and id= 1

很明显这类直接拼接值很轻易形成SQL注入,如果我们传入的参数为name值为 chenhao'; DROP TABLE user;#  ,剖析获得的效果为

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

由于传入的参数没有经由转义,终究致使了一条 SQL 被歹意参数拼接成了两条 SQL。这就是为何我们不该该在 SQL 语句中是用 ${} 占位符,风险太大。接着我们来看看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 表达式的效果
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            // 若 test 表达式中的前提建立,则挪用其子节点节点的 apply 要领举行剖析
            // 如果是静态SQL节点,则会直接拼接到DynamicContext中
            contents.apply(context);
            return true;
        }
        return false;
    }
}

IfSqlNode 对应的是 <if test='xxx'> 节点,首先是经由历程 ONGL 检测 test 表达式是不是为 true,如果为 true,则挪用其子节点的 apply 要领继续举行剖析。如果子节点是静态SQL节点,则子节点的文本值会直接拼接到DynamicContext中

好了,其他的SqlNode我就不逐一剖析了,人人有兴致的能够去看看

剖析 #{} 占位符

经由前面的剖析,我们已能从 DynamicContext 猎取到完全的 SQL 语句了。但这并不意味着剖析历程就完毕了,由于当前的 SQL 语句中另有一种占位符没有处置惩罚,即 #{}。与 ${} 占位符的处置惩罚方式差别,MyBatis 并不会直接将 #{} 占位符替代为响应的参数值,而是将其替代成。其剖析是在以下代码中完成的

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

我们看到将前面剖析过的sql字符串和运转时参数的Map作为参数,我们来看看parse要领

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    // 建立 #{} 占位符处置惩罚器
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 建立 #{} 占位符剖析器
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 剖析 #{} 占位符,并返回剖析效果字符串
    String sql = parser.parse(originalSql); // 封装剖析效果到 StaticSqlSource 中,并返回,由于一切的动态参数都已剖析了,能够封装成一个静态的SqlSource
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

public String handleToken(String content) {
    // 猎取 content 的对应的 ParameterMapping
 parameterMappings.add(buildParameterMapping(content)); // 返回 ?
    return "?";
}

我们看到将Sql中的 #{} 占位符替代成"?",而且将对应的参数转化成ParameterMapping 对象,经由历程buildParameterMapping 完成,末了建立一个StaticSqlSource,将sql字符串和ParameterMappings为参数传入,返回这个StaticSqlSource

private ParameterMapping buildParameterMapping(String content) {
    /*
     * 将#{xxx} 占位符中的内容剖析成 Map。
     *   #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
     *      上面占位符中的内容终究会被剖析成以下的效果:
     *  {
     *      "property": "age",
     *      "typeHandler": "MyTypeHandler", 
     *      "jdbcType": "NUMERIC", 
     *      "javaType": "int"
     *  }
     */
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    // metaParameters 为 DynamicContext 成员变量 bindings 的元信息对象
    if (metaParameters.hasGetter(property)) {
        propertyType = metaParameters.getGetterType(property);
    
    /*
     * parameterType 是运转时参数的范例。如果用户传入的是单个参数,比方 Employe 对象,此时 
     * parameterType 为 Employe.class。如果用户传入的多个参数,比方 [id = 1, author = "chenhao"],
     * MyBatis 会运用 ParamMap 封装这些参数,此时 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 是一个自定义的类,
         * 比方 Employe,此时为该类建立一个元信息对象
         */
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        // 检测参数对象有无与 property 想对应的 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,则以用户的设置为准
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
            // 剖析 jdbcType
            builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {...} 
        else if ("numericScale".equals(name)) {...} 
        else if ("resultMap".equals(name)) {...} 
        else if ("typeHandler".equals(name)) {
            typeHandlerAlias = value;    
        } 
        else if ("jdbcTypeName".equals(name)) {...} 
        else if ("property".equals(name)) {...} 
        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));
    }
    
    // 构建 ParameterMapping 对象
    return builder.build();
}

SQL 中的 #{name, ...} 占位符被替代成了问号 ?。#{name, ...} 也被剖析成了一个 ParameterMapping 对象。我们再来看一下 StaticSqlSource 的建立历程。以下:

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) {
        // 建立 BoundSql 对象
        return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }
}

末了我们经由历程建立的StaticSqlSource就能够猎取BoundSql对象了,并传入运转时参数

BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

也就是挪用上面建立的StaticSqlSource 中的getBoundSql要领,这是简朴的 return new BoundSql(configuration, sql, parameterMappings, parameterObject); ,接着看看BoundSql

public class BoundSql {
    private String sql; private List<ParameterMapping> parameterMappings; private Object parameterObject; private Map<String, Object> additionalParameters;
    private 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();
        this.metaParameters = configuration.newMetaObject(this.additionalParameters);
    }

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

我们看到只是做简朴的赋值。BoundSql中包含了sql,#{}剖析成的parameterMappings,另有运转时参数parameterObject。好了,SQL剖析我们就引见这么多。我们先回忆一下我们代码是从那里最先的

CachingExecutor

1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
2     // 猎取 BoundSql
3     BoundSql boundSql = ms.getBoundSql(parameterObject); 4    // 建立 CacheKey
5     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
6     // 挪用重载要领
7     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
8 }

如上,我们适才都是剖析的第三行代码,猎取到了BoundSql,CacheKey 和二级缓存有关,我们留在下一篇文章零丁来说,接着我们看第七行重载要领 query

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 从 MappedStatement 中猎取缓存
    Cache cache = ms.getCache();
    // 若映照文件中未设置缓存或参照缓存,此时 cache = null
    if (cache != null) {
        flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
                // 若缓存未掷中,则挪用被装潢类的 query 要领,也就是SimpleExecutor的query要领
                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);
}

上面的代码触及到了二级缓存,若二级缓存为空,或未掷中,则挪用被装潢类的 query 要领。被装潢类为SimpleExecutor,而SimpleExecutor继续BaseExecutor,那我们来看看 BaseExecutor 的query要领

BaseExecutor

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    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();
        }
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache();
        }
    }
    return list;
}

从一级缓存中查找查询效果。若缓存未掷中,再向数据库举行查询。至此我们邃晓了一级二级缓存的也许思绪,先从二级缓存中查找,若未掷中二级缓存,再从一级缓存中查找,若未掷中一级缓存,再从数据库查询数据,那我们来看看是怎样从数据库查询的

BaseExecutor

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 {
        // 挪用 doQuery 举行查询
        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;
}

挪用了doQuery要领举行查询,末了将查询效果放入一级缓存,我们来看看doQuery,在SimpleExecutor中

SimpleExecutor

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
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 建立 Statement
        stmt = prepareStatement(handler, ms.getStatementLog()); // 实行查询操纵
        return handler.<E>query(stmt, resultHandler);
    } finally {
        // 封闭 Statement
        closeStatement(stmt);
    }
}

我们先来看看第一步建立StatementHandler 

建立StatementHandler 

StatementHandler有什么作用呢?经由历程这个对象猎取Statement对象,然后添补运转时参数,末了挪用query完成查询。我们来看看其建立历程

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

我们看看RoutingStatementHandler的组织要领

public class RoutingStatementHandler implements StatementHandler {

    private final StatementHandler delegate;

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

        // 依据 StatementType 建立差别的 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 的组织要领会依据 MappedStatement 中的 statementType 变量建立差别的 StatementHandler 完成类。那statementType 是什么呢?我们还要回忆一下MappedStatement 的建立历程

 

我们看到statementType 的默许范例为PREPARED,这里将会建立PreparedStatementHandler。

接着我们看下面一行代码prepareStatement,

建立 Statement

建立 Statement 在 stmt = prepareStatement(handler, ms.getStatementLog()); 这句代码,那我们跟进去看看

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 猎取数据库衔接
    Connection connection = getConnection(statementLog); // 建立 Statement,
    stmt = handler.prepare(connection, transaction.getTimeout()); // 为 Statement 设置参数
 handler.parameterize(stmt); return stmt;
}

在上面的代码中我们终究看到了和jdbc相干的内容了,建立完Statement,末了就能够实行查询操纵了。由于篇幅的缘由,我们留在下一篇文章再来细致解说

 

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
Mybaits 源码剖析 (六)----- Select 语句的实行历程剖析(上篇)

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282244.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>