mybatis $和#源代码分析
JDBC中,主要使用兩種語句,一種是支持參數(shù)化和預(yù)編譯的PreparedStatement,支持原生sql,支持設(shè)置占位符,參數(shù)化輸入的參數(shù),防止sql注入攻擊,在mybatis的mapper配置文件中,我們通過使用#和$告訴mybatis我們需要對(duì)參數(shù)進(jìn)行怎樣的設(shè)置。sql注入指的是利用現(xiàn)有應(yīng)用程序?qū)阂獾膕ql命令提交存在安全漏洞。例如在提交表單時(shí)加入or拼接語句使其永遠(yuǎn)成立。對(duì)比JDBC執(zhí)行流程connection->statement->result,在mybatis中由SqlSession提供給用戶操作的API,Excutor具體執(zhí)行數(shù)據(jù)庫的操作。
SqlSession接口主要的實(shí)現(xiàn)類有:
先看DefaultSqlSession:
selectOne的方法最終轉(zhuǎn)化成了selectList方法
最終執(zhí)行的是箭頭指的selectList方法,該方法中含有變量:MappedSatement和Excutor,MappedSatement是加載mapper.xml時(shí)匹配的namespace+id
StrictMap是Configuration類中的一個(gè)靜態(tài)內(nèi)部類,繼承了HashMap,看一下selectList方法調(diào)用的wrapCollection方法
這就是為什么我們?cè)趍apper.xml中foreach用list或array遍歷,再看Excutor執(zhí)行的query方法,Excutor接口實(shí)現(xiàn)類
BaseExcutor的query方法:
由上可知若設(shè)置清除緩存,首先會(huì)清除緩存,首先會(huì)根據(jù)CacheKey查找緩存,查找結(jié)果為空,則從數(shù)據(jù)庫查
?
doQuery:
可見Executor委托給StatementHandler執(zhí)行查詢,在此之前有一個(gè)預(yù)編譯的過程(prepareStatement方法),StatementHandler接口的實(shí)現(xiàn)類:CallableStatementHandler,PreparedStatementHandler,SimpleStatementHandler對(duì)應(yīng)JDBC中的CallableStatement,PreparedStatement和Statement,分別的執(zhí)行方法:
看之前提到的prepareStatement方法
handler會(huì)對(duì)statement參數(shù)化設(shè)置,PreparedStatementHandler中:
由parameterhandler執(zhí)行參數(shù)設(shè)置,上面是簡(jiǎn)單分析的查詢流程,回頭說$和#,我們知道,使用$時(shí)statement執(zhí)行的是拼接操作,#的時(shí)候statement用的是占位符 ?,這是mybatis解析的時(shí)候造成的,根據(jù)測(cè)試?yán)?#xff1a;
SqlSessionFactoryBuilder的build方法:
XMLConfigBuilder負(fù)責(zé)解析總配置文件,其中方法有:
返回值都為XNode節(jié)點(diǎn)類型,看mapperElement:
package掃描包,resource和class掃描指定類和mapper.xml,XMLMapperBuilder:
主要看這兩個(gè)方法,根據(jù)節(jié)點(diǎn)建立statement:
由上可以看出XMLStatementBuilder解析statement,也就是mapper.xml中的一個(gè)個(gè)statement,快成功了。。。
parseStatementNode解析一條記錄中的各個(gè)屬性,例如resultType,parameterType,useCache等等。。。該方法代碼過長,其主要在:
兩處,一個(gè)是SqlSource,一個(gè)是addMappedStatement,解析的屬性值都對(duì)應(yīng)到MappedStatement對(duì)象中
在MappedStatement對(duì)象中除這些外,還有個(gè)屬性SqlSource,可見該對(duì)象決定sql語句的解析
只有一個(gè)抽象方法getBoundSql,SqlSource是如何獲取的呢,這就用到了上面prepareStatement方法中的LanguageDriver的createSqlSource方法,繼續(xù)跟進(jìn):
解析sql語句之前會(huì)先解析selectKey和include節(jié)點(diǎn),LanguageDriver的實(shí)現(xiàn)類有XMLLanguageDriver和RawLanguageDriver
可以知道createSqlSource方法只在XMLLanguageDriver實(shí)現(xiàn)
委托給了XMLScriptBuilder的parseScriptNode方法:
1 public SqlSource parseScriptNode() { 2 List<SqlNode> 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 }根據(jù)isDynamic標(biāo)志確定sqlSource類型,parseDynamicTags方法:
1 private List<SqlNode> parseDynamicTags(XNode node) { 2 List<SqlNode> contents = new ArrayList<SqlNode>(); 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 #628 16 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 }返回值是一個(gè)list,過程:
?遍歷各個(gè)子節(jié)點(diǎn)
(1) 如果節(jié)點(diǎn)類型是文本或者CDATA,構(gòu)造一個(gè)TextSqlNode或StaticTextSqlNode
(2) 如果節(jié)點(diǎn)類型是元素,說明該節(jié)點(diǎn)是個(gè)動(dòng)態(tài)sql,然后會(huì)使用NodeHandler處理各個(gè)類型的子節(jié)點(diǎn)。這里的NodeHandler是XMLScriptBuilder的一個(gè)內(nèi)部接口,其實(shí)現(xiàn)類包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看類名也就明白了這個(gè)Handler的作用,比如我們分析的trim節(jié)點(diǎn),對(duì)應(yīng)的是TrimHandler;if節(jié)點(diǎn),對(duì)應(yīng)的是IfHandler...,TextSqlNode的isDynamic方法:
1 public boolean isDynamic() { 2 DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser(); 3 GenericTokenParser parser = createParser(checker);//建立GenericTokenParser 4 parser.parse(text);//GenericTokenParser解析text 5 return checker.isDynamic(); 6 }createParser方法:
?1 private GenericTokenParser createParser(TokenHandler handler) { 2 return new GenericTokenParser("${", "}", handler); 3 }?
構(gòu)造方法:
1 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { 2 this.openToken = openToken; 3 this.closeToken = closeToken; 4 this.handler = handler; 5 }根據(jù)是否Dynamic,TokenHandler的主要實(shí)現(xiàn)類有:DynamicCheckerTokenParser和ParameterMappingTokenHandler,VariableTokenHandler
?1 private GenericTokenParser createParser(TokenHandler handler) { 2 return new GenericTokenParser("${", "}", handler); 3 }?//$的處理方式
DynamicCheckerTokenParser實(shí)現(xiàn)的handleToken方法
1 public String handleToken(String content) { 2 this.isDynamic = true; 3 return null; 4 }ParameterMappingTokenHandler實(shí)現(xiàn)的handleToken方法:
1 public String handleToken(String content) {//#的處理方式,返回占位符? 2 parameterMappings.add(buildParameterMapping(content)); 3 return "?"; 4 }#的方式大概就是這樣,再看$,$使用的是DynamicCheckerTokenParser,這時(shí)候再看返回的DynamicSqlSource,其實(shí)現(xiàn)SqlSource接口的getBoundSql方法:
1 public BoundSql getBoundSql(Object parameterObject) { 2 DynamicContext context = new DynamicContext(configuration, parameterObject); 3 rootSqlNode.apply(context);//apply方法實(shí)際調(diào)用的是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<String, Object> 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方法,此時(shí)的handler為BindingTokenParser 22 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不太清除,先分析到這
轉(zhuǎn)載于:https://www.cnblogs.com/miserable-faith/p/7658550.html
總結(jié)
以上是生活随笔為你收集整理的mybatis $和#源代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POJ 2389Bull Math(水~
- 下一篇: 关于.c和.h 和定义变量的问题