博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
mybatis $和#源代码分析
阅读量:4642 次
发布时间:2019-06-09

本文共 8197 字,大约阅读时间需要 27 分钟。

JDBC中,主要使用两种语句,一种是支持参数化和预编译的PreparedStatement,支持原生sql,支持设置占位符,参数化输入的参数,防止sql注入攻击,在mybatis的mapper配置文件中,我们通过使用#和$告诉mybatis我们需要对参数进行怎样的设置。sql注入指的是利用现有应用程序将恶意的sql命令提交存在安全漏洞。例如在提交表单时加入or拼接语句使其永远成立。对比JDBC执行流程connection->statement->result,在mybatis中由SqlSession提供给用户操作的API,Excutor具体执行数据库的操作。

SqlSession接口主要的实现类有:

先看DefaultSqlSession:

selectOne的方法最终转化成了selectList方法

最终执行的是箭头指的selectList方法,该方法中含有变量:MappedSatement和Excutor,MappedSatement是加载mapper.xml时匹配的namespace+id

StrictMap是Configuration类中的一个静态内部类,继承了HashMap,看一下selectList方法调用的wrapCollection方法

这就是为什么我们在mapper.xml中foreach用list或array遍历,再看Excutor执行的query方法,Excutor接口实现类

BaseExcutor的query方法:

由上可知若设置清除缓存,首先会清除缓存,首先会根据CacheKey查找缓存,查找结果为空,则从数据库查

 

doQuery:

可见Executor委托给StatementHandler执行查询,在此之前有一个预编译的过程(prepareStatement方法),StatementHandler接口的实现类:CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler对应JDBC中的CallableStatement,PreparedStatement和Statement,分别的执行方法:

看之前提到的prepareStatement方法

handler会对statement参数化设置,PreparedStatementHandler中:

由parameterhandler执行参数设置,上面是简单分析的查询流程,回头说$和#,我们知道,使用$时statement执行的是拼接操作,#的时候statement用的是占位符 ?,这是mybatis解析的时候造成的,根据测试例子:

SqlSessionFactoryBuilder的build方法:

XMLConfigBuilder负责解析总配置文件,其中方法有:

返回值都为XNode节点类型,看mapperElement:

package扫描包,resource和class扫描指定类和mapper.xml,XMLMapperBuilder:

主要看这两个方法,根据节点建立statement:

由上可以看出XMLStatementBuilder解析statement,也就是mapper.xml中的一个个statement,快成功了。。。

parseStatementNode解析一条记录中的各个属性,例如resultType,parameterType,useCache等等。。。该方法代码过长,其主要在:

两处,一个是SqlSource,一个是addMappedStatement,解析的属性值都对应到MappedStatement对象中

在MappedStatement对象中除这些外,还有个属性SqlSource,可见该对象决定sql语句的解析

只有一个抽象方法getBoundSql,SqlSource是如何获取的呢,这就用到了上面prepareStatement方法中的LanguageDriver的createSqlSource方法,继续跟进:

解析sql语句之前会先解析selectKey和include节点,LanguageDriver的实现类有XMLLanguageDriver和RawLanguageDriver

可以知道createSqlSource方法只在XMLLanguageDriver实现

委托给了XMLScriptBuilder的parseScriptNode方法:

1 public SqlSource parseScriptNode() { 2     List
contents = parseDynamicTags(context); 3 MixedSqlNode rootSqlNode = new MixedSqlNode(contents); 4 SqlSource sqlSource = null; 5 if (isDynamic) { 6 sqlSource = new DynamicSqlSource(configuration, rootSqlNode); 7 } else { 8 sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); 9 }10 return sqlSource;11 }

根据isDynamic标志确定sqlSource类型,parseDynamicTags方法:

1  private List
parseDynamicTags(XNode node) { 2 List
contents = new ArrayList
(); 3 NodeList children = node.getNode().getChildNodes(); 4 for (int i = 0; i < children.getLength(); i++) { 5 XNode child = node.newXNode(children.item(i)); 6 if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { 7 String data = child.getStringBody(""); 8 TextSqlNode textSqlNode = new TextSqlNode(data); 9 if (textSqlNode.isDynamic()) {10 contents.add(textSqlNode);11 isDynamic = true;12 } else {13 contents.add(new StaticTextSqlNode(data));14 }15 } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #62816 String nodeName = child.getNode().getNodeName();17 NodeHandler handler = nodeHandlers.get(nodeName);18 if (handler == null) {19 throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");20 }21 handler.handleNode(child, contents);22 isDynamic = true;23 }24 }25 return contents;26 }

返回值是一个list,过程:

 遍历各个子节点

(1) 如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode

(2) 如果节点类型是元素,说明该节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点。这里的NodeHandler是XMLScriptBuilder的一个内部接口,其实现类包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看类名也就明白了这个Handler的作用,比如我们分析的trim节点,对应的是TrimHandler;if节点,对应的是IfHandler...,TextSqlNode的isDynamic方法:

1   public boolean isDynamic() {2     DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();3     GenericTokenParser parser = createParser(checker);//建立GenericTokenParser4     parser.parse(text);//GenericTokenParser解析text5     return checker.isDynamic();6   }

createParser方法:

 1 private GenericTokenParser createParser(TokenHandler handler) { 2 return new GenericTokenParser("${", "}", handler); 3 } 

构造方法:

1   public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {2     this.openToken = openToken;3     this.closeToken = closeToken;4     this.handler = handler;5   }

根据是否Dynamic,TokenHandler的主要实现类有:DynamicCheckerTokenParser和ParameterMappingTokenHandler,VariableTokenHandler

 1 private GenericTokenParser createParser(TokenHandler handler) { 2 return new GenericTokenParser("${", "}", handler); 3 } //$的处理方式

DynamicCheckerTokenParser实现的handleToken方法

1  public String handleToken(String content) {2       this.isDynamic = true;3       return null;4     }

ParameterMappingTokenHandler实现的handleToken方法:

1  public String handleToken(String content) {
//#的处理方式,返回占位符?2 parameterMappings.add(buildParameterMapping(content));3 return "?";4 }

#的方式大概就是这样,再看$,$使用的是DynamicCheckerTokenParser,这时候再看返回的DynamicSqlSource,其实现SqlSource接口的getBoundSql方法:

1 public BoundSql getBoundSql(Object parameterObject) { 2     DynamicContext context = new DynamicContext(configuration, parameterObject); 3     rootSqlNode.apply(context);//apply方法实际调用的是TextSqlNode的applay方法 4     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); 5     Class
parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); 6 SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());//SqlSourceBuilder的parse方法解析 7 BoundSql boundSql = sqlSource.getBoundSql(parameterObject); 8 for (Map.Entry
entry : context.getBindings().entrySet()) { 9 boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());10 }11 return boundSql;12 }
TextSqlNode的apply方法:
1 public boolean apply(DynamicContext context) {2     GenericTokenParser parser = createParser(new BindingTokenParser(context));3     context.appendSql(parser.parse(text));4     return true;5   }

用的是BindingTokenParser的parse方法:

1  public String parse(String text) { 2     StringBuilder builder = new StringBuilder(); 3     if (text != null && text.length() > 0) { 4       char[] src = text.toCharArray(); 5       int offset = 0; 6       int start = text.indexOf(openToken, offset); 7       while (start > -1) { 8         if (start > 0 && src[start - 1] == '\\') { 9           // the variable is escaped. remove the backslash.10           builder.append(src, offset, start - 1).append(openToken);11           offset = start + openToken.length();12         } else {13           int end = text.indexOf(closeToken, start);14           if (end == -1) {15             builder.append(src, offset, src.length - offset);16             offset = src.length;17           } else {18             builder.append(src, offset, start - offset);19             offset = start + openToken.length();20             String content = new String(src, offset, end - offset);21             builder.append(handler.handleToken(content));//又回到了handleToken方法,此时的handler为BindingTokenParser22             offset = end + closeToken.length();23           }24         }25         start = text.indexOf(openToken, offset);26       }27       if (offset < src.length) {28         builder.append(src, offset, src.length - offset);29       }30     }31     return builder.toString();32   }

BindingTokenParser的handleToken:

1 public String handleToken(String content) { 2       Object parameter = context.getBindings().get("_parameter"); 3       if (parameter == null) { 4         context.getBindings().put("value", null); 5       } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { 6         context.getBindings().put("value", parameter);//从此处可以看出mapper.xml中$或#中可以用value 7       } 8       Object value = OgnlCache.getValue(content, context.getBindings());//此处用了ognl处理 9       return (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"10     }

ognl不太清除,先分析到这

 

转载于:https://www.cnblogs.com/miserable-faith/p/7658550.html

你可能感兴趣的文章
bzoj3238 [Ahoi2013]差异
查看>>
ASP.NET常见面试题及答案(130题)
查看>>
初学CDQ分治-NEU1702
查看>>
React组件的生命周期
查看>>
java笔记--使用SwingWoker类完成耗时操作
查看>>
Android应用程序后台加载数据
查看>>
2016北京集训测试赛(九)Problem C: 狂飙突进的幻想乡
查看>>
CentOS6.5手动升级gcc4.8.2
查看>>
3.9 java基础总结集合①LIst②Set③Map④泛型⑤Collections
查看>>
Unix和Linux下C语言学习指南
查看>>
linux指令
查看>>
linux下面升级 Python版本并修改yum属性信息
查看>>
局域网内通讯APP
查看>>
Unity Shader 图片流光效果实现(纯计算方式)
查看>>
POJ 2002 Squares
查看>>
Java 内存分配
查看>>
ObjectDataSource控件执行Delete操作时,出现“未能找到带参数的非泛型方法”的解决方案...
查看>>
Ubuntu17.10 React Native 环境搭建
查看>>
Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结
查看>>
jQuery-2.1.4.min.js:4 Uncaught TypeError: Illegal invocation
查看>>