入参${}的解析

List<User> get(Integer id);
<select id="get" resultType="com.entity.User">select * from user where id = ${id}</select>

这个例子,我们没有在全局变量中定义id,而是在方法中传入这个值。根据上文中的VariableTokenHandler.handleToken方法就会返回${id},表示这个参数全局变量中没有,是待解析的参数。

这是解析buildStatementFromContext(context.evalNodes("select|insert|update|delete"));的后续代码,用来解析标签,并创建mappedStaement,这里直接copy过来.

//XMLStatementBuilder.parseStatementNodepublic void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否刷新缓存 默认值:增删改刷新 查询不刷新boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//是否使用二级缓存 默认值:查询使用 增删改不使用boolean useCache = context.getBooleanAttribute("useCache", isSelect);//是否需要处理嵌套查询结果 group by// 三组数据 分成一个嵌套的查询结果boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);//替换Includes标签为对应的sql标签里面的值includeParser.applyIncludes(context.getNode());String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);//解析配置的自定义脚本语言驱动 mybatis plusString lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.//解析selectKeyprocessSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)//设置主键自增规则KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}//解析Sql  根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));//暗示驱动程序每次批量返回的结果行数Integer fetchSize = context.getIntAttribute("fetchSize");//超时时间Integer timeout = context.getIntAttribute("timeout");//引用外部 parameterMap,已废弃String parameterMap = context.getStringAttribute("parameterMap");//结果类型String resultType = context.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);//引用外部的 resultMapString resultMap = context.getStringAttribute("resultMap");//结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

找到解析sql的部分具体来分析,一层一层往下。

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

RawLanguageDriver.createSqlSource 该类是XMLLanguageDriver的子类

@Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {SqlSource source = super.createSqlSource(configuration, script, parameterType);checkIsNotDynamic(source);return source;}

XMLLanguageDriver.createSqlSource

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}

XMLScriptBuilder.parseScriptNode

public SqlSource parseScriptNode() {MixedSqlNode rootSqlNode = parseDynamicTags(context);SqlSource sqlSource;//判断节点是否是动态的,包含是否包含if、where 、choose、trim、foreach、bind、sql标签,这个例子中我们进入elseif (isDynamic) {//不解析sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {//用占位符方式来解析sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;}

这里进行判断isDynamic的值,这个方法我们只需要关注textSqlNode.isDynamic()就行了。代码与之前解析node有些类似。

protected MixedSqlNode parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList<>();NodeList children = node.getNode().getChildNodes();for (int i = 0; i < children.getLength(); i++) {//注意!!这里又new了一个XNode,也就是说,这个节点中的sql语句又被解析了一次,解析方式和上文从同全局获取变量一样。//与上文不同的是,这里传入的是子节点,也就是sql文本语句,而上文解析的是整个select元素//这个child是临时变量,节点解析的结果不做保存XNode child = node.newXNode(children.item(i));//判断节点类型if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {String data = child.getStringBody("");TextSqlNode textSqlNode = new TextSqlNode(data);//这里判断语句是否是动态的if (textSqlNode.isDynamic()) {contents.add(textSqlNode);isDynamic = true;} else {contents.add(new StaticTextSqlNode(data));}} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628String nodeName = child.getNode().getNodeName();NodeHandler handler = nodeHandlerMap.get(nodeName);if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");}handler.handleNode(child, contents);isDynamic = true;}}return new MixedSqlNode(contents);}

TextSqlNode.isDynamic

public boolean isDynamic() {DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();//这里创建一个解析器进行解析sql语句,这里解析的是仍然是${}GenericTokenParser parser = createParser(checker);parser.parse(text);return checker.isDynamic();}
private GenericTokenParser createParser(TokenHandler handler) {return new GenericTokenParser("${", "}", handler);}

熟悉的代码,还是同样的解析器,用来处理${,和},不过这次的hander不同,为DynamicCheckerTokenParser

//DynamicCheckerTokenParser.handleTokenpublic String handleToken(String content) {this.isDynamic = true;return null;}
}

这次的处理方式是将直接返回空,也就是说,sql会变成 select * from user where id = null。但是返回的结果并没有被保存,parser.parse(text)并没有参数来接受它的返回值,所以这里只是用来更新isDynamic参数。

回到XMLScriptBuilder.parseScriptNode方法,这里根据isDynamic的布尔值,会有两种SqlSource.DynamicSqlSource和RawSqlSource。到这里配置文件就解析完成了,后续sql中的参数都是从方法中获取的,所以只能在执行的时候动态进行替换。

来到query查询方法。ms.getBoundSql会获取绑定的封装sql.

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

MappedStatement.getBoundSql

public BoundSql getBoundSql(Object parameterObject) {//获取绑定的sqlBoundSql boundSql = sqlSource.getBoundSql(parameterObject);//获取sql中对应的参数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;}
//DynamicSqlSource.getBoundSql。public BoundSql getBoundSql(Object parameterObject) {//parameterObject中有我们方法传入的参数DynamicContext context = new DynamicContext(configuration, parameterObject);//这里解析${}rootSqlNode.apply(context);SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());BoundSql boundSql = sqlSource.getBoundSql(parameterObject);context.getBindings().forEach(boundSql::setAdditionalParameter);return boundSql;}

为什么是DynamicSqlSource而不是RawSqlSource,这个前文分析过,在替换完全局变量后,语句中如果还包含${},使用的就是DynamicSqlSource。

//MixedSqlNode.applypublic boolean apply(DynamicContext context) {contents.forEach(node -> node.apply(context));return true;}

TextSqlNode.apply

@Overridepublic boolean apply(DynamicContext context) {contents.forEach(node -> node.apply(context));return true;}

这里再次创建了${}的解析器,这次的handler是BindingTokenParser

public boolean apply(DynamicContext context) {GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));context.appendSql(parser.parse(text));return true;}private GenericTokenParser createParser(TokenHandler handler) {return new GenericTokenParser("${", "}", handler);}

BindingTokenParser.handleToken,如果sql中存在${},就会将其替换成具体的参数,语句就变成 select * from user where id = 1,就能直接执行了

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);}Object value = OgnlCache.getValue(content, context.getBindings());String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"checkInjection(srtValue);return srtValue;}

需要更多教程,微信扫码即可

Mybatis源码解析:sql参数处理(2)相关推荐

  1. Mybatis源码解析-sql执行

    一.传统的jdbc操作步骤 获取驱动 获取jdbc连接 创建参数化预编译的sql 绑定参数 发送sql到数据库执行 将将获取到的结果集返回应用 关闭连接 传统的jdbc代码: package com. ...

  2. MyBatis 源码分析 - SQL 的执行过程

    本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...

  3. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  4. mybatis源码解析(一)

    Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...

  5. Mybatis源码解析《二》

    导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...

  6. mybatis源码解析一 xml解析(解析器)

    最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...

  7. Mybatis源码解析(一):环境搭建

    Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...

  8. mybatis源码解析1_sessionFactory

    注 一下内容都是根据尚硅谷2018年mybatis源码解析做的课程笔记,视频链接: https://www.bilibili.com/video/BV1mW411M737?p=74&share ...

  9. 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖

    移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...

  10. [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构

    [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 文章目录 [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 0x00 摘要 0x01使用 1.1 ...

最新文章

  1. 数据库弱一致性四个隔离级别
  2. 《LeetCode力扣练习》第46题 全排列 Java
  3. Python中的输入输出
  4. 对付网络盗贼的三板斧
  5. PHPRunner(网页制作工具)v10.3中文版
  6. 【LeetCode从零单排】No.9 Palindrome Number
  7. PHP进行生成并且导出CSV文件
  8. 入驻支付宝开放平台并创建应用的基本流程
  9. MonoRail学习笔记十一:页面控件的填充和验证
  10. Android之PowerManager简介
  11. java反编译器JAD.exe的使用
  12. Hosts Setup Utility – 在线更新 hosts
  13. kafka-spark-streaming-mysql(scala)实时数据处理案列
  14. coolfire文章之五
  15. 高等数学 下册 第九章 多元函数的概念 笔记
  16. 『Others』WPS广告关闭
  17. 四年级计算机上册说课ppt,四年级上册《画长方形》说课稿
  18. 根据线索整理的一套在2021年继续使用Flash Player的方法(20.12.29更新)
  19. win10安装apache环境
  20. 目前中国计算机水平如何,中国现在计算机水平现状是怎样的

热门文章

  1. 儿童手表还能这么用?定位功能防出轨 网友:这令人窒息的爱情
  2. 它来了,带着曝光图又来了!疑似小米MIX4谍照流出
  3. 签约沈腾,易车开启三年品牌计划,穿越车市寒冬
  4. 最耐用的手机盘点 网友:我这个能用到品牌商“破产”!
  5. 你的押金在这?ofo发文曝光多起贪腐案:总涉案金额达数百万元
  6. 三星S10+顶配版现身GeekBench:搭载Exynos 9820处理器
  7. redis订阅怎么退出_关于redis,学会这8点就够了
  8. 小女出世,暂停工作,全职照料大人小孩
  9. FTP主动模式和被动模式学习笔记
  10. 查询端口号是否被占用指令