Mybatis源码解析:sql参数处理(2)
入参${}的解析
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)相关推荐
- Mybatis源码解析-sql执行
一.传统的jdbc操作步骤 获取驱动 获取jdbc连接 创建参数化预编译的sql 绑定参数 发送sql到数据库执行 将将获取到的结果集返回应用 关闭连接 传统的jdbc代码: package com. ...
- MyBatis 源码分析 - SQL 的执行过程
本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析.运 ...
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- mybatis源码解析(一)
Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...
- Mybatis源码解析《二》
导语 在前一篇文章Mybatis源码解析<一>中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文 ...
- mybatis源码解析一 xml解析(解析器)
最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...
- Mybatis源码解析(一):环境搭建
Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...
- mybatis源码解析1_sessionFactory
注 一下内容都是根据尚硅谷2018年mybatis源码解析做的课程笔记,视频链接: https://www.bilibili.com/video/BV1mW411M737?p=74&share ...
- 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖
移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...
- [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构
[源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 文章目录 [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构 0x00 摘要 0x01使用 1.1 ...
最新文章
- 数据库弱一致性四个隔离级别
- 《LeetCode力扣练习》第46题 全排列 Java
- Python中的输入输出
- 对付网络盗贼的三板斧
- PHPRunner(网页制作工具)v10.3中文版
- 【LeetCode从零单排】No.9 Palindrome Number
- PHP进行生成并且导出CSV文件
- 入驻支付宝开放平台并创建应用的基本流程
- MonoRail学习笔记十一:页面控件的填充和验证
- Android之PowerManager简介
- java反编译器JAD.exe的使用
- Hosts Setup Utility – 在线更新 hosts
- kafka-spark-streaming-mysql(scala)实时数据处理案列
- coolfire文章之五
- 高等数学 下册 第九章 多元函数的概念 笔记
- 『Others』WPS广告关闭
- 四年级计算机上册说课ppt,四年级上册《画长方形》说课稿
- 根据线索整理的一套在2021年继续使用Flash Player的方法(20.12.29更新)
- win10安装apache环境
- 目前中国计算机水平如何,中国现在计算机水平现状是怎样的