当前位置:首页 > 数码 > Mybatis占位符与$的区别-源码解读 (mybatis)

Mybatis占位符与$的区别-源码解读 (mybatis)

admin4个月前 (05-12)数码16

本文针对笔者日常开发中对占位符#{}和${}经常使用机遇联合源码,思索总结而来

一.启动时,mybatis-spring解析xml文件流程图

Spring名目启动时,mybatis-spring智能初始化解析xml文件外围流程。

流程图

Mybatis在buildSqlSessionFactory()会遍历一切merLocations(xml文件)调用xmlMapperBuilder.parse()解析,源码如下:

在parse()方法中,Mybatis经过configurationElement(parser.evalNode("/mapper"))方法解析xml文件中的各个标签。

publicclassXMLMapperBuilderextendsBaseBuilder{...privatefinalMapperBuilderAssistantbuilderAssistant;privatefinalMap<String,XNode>sqlFragments;...publicvoidparse(){if(!configuration.isResourceLoaded(resource)){//xml文件解析逻辑configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}privatevoidconfigurationElement(XNodecontext){try{//解析xml文件内的namespace、cache-ref、cache、parameterMap、resultMap、sql、select、insert、update、delete等各种标签Stringnamespace=context.getStringAttribute("namespace");if(namespace==null||namespace.isEmpty()){thrownewBuilderException("Mapper'snamespacecannotbeempty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));buildStatementFromContext(context.evalNodes("select|insert|update|delete"));}catch(Exceptione){thrownewBuilderException("ErrorparsingMapperXML.TheXMLlocationis'"+resource+"'.Cause:"+e,e);}}}

最后会把namespace、cache-ref、cache、parameterMap、resultMap、select、insert、update、delete等标签内容解析结果放到builderAssistant对象中,将sql标签解析结果放到sqlFragments对象中,其中因为builderAssistant对象会保留select、insert、update、delete标签内容解析结果咱们对builderAssistant对象启动深化了解。

publicclassMapperBuilderAssistantextendsBaseBuilder{...}publicabstractclassBaseBuilder{protectedfinalConfigurationconfiguration;...}publicclassConfiguration{...protectedfinalMap<String,MappedStatement>mappedStatements=newStrictMap<MappedStatement>("MappedStatementscollection").conflictMessageProducer((savedValue,targetValue)->".pleasecheck"+savedValue.getResource()+"and"+targetValue.getResource());protectedfinalMap<String,Cache>caches=newStrictMap<>("Cachescollection");protectedfinalMap<String,ResultMap>resultMaps=newStrictMap<>("ResultMapscollection");protectedfinalMap<String,ParameterMap>parameterMaps=newStrictMap<>("ParameterMapscollection");protectedfinalMap<String,KeyGenerator>keyGenerators=newStrictMap<>("KeyGeneratorscollection");protectedfinalSet<String>loadedResources=newHashSet<>();protectedfinalMap<String,XNode>sqlFragments=newStrictMap<>("XMLfragmentsparsedfrompreviousmappers");...}

builderAssistant对象承袭至BaseBuilder,BaseBuilder类中蕴含一个configuration对象属性,configuration对象中会保留xml文件标签解析结果至自身对应属性mappedStatements、caches、resultMaps、sqlFragments。

这里有个疑问下面提到的sql标签结果会放到XMLMapperBuilder类的sqlFragments对象中,为什么Configuration类中也有个sqlFragments属性?

这里回看上文buildSqlSessionFactory()方法最后。

原来XMLMapperBuilder类中的sqlFragments属性就来自Configuration类。

回到主题,在buildStatementFromContext(context.evalNodes("select|insert|update|delete"))方法中会经过如下调用。

buildStatementFromContext(List<XNode>list,StringrequiredDatabaseId)->parseStatementNode()->createSqlSource(Configurationconfiguration,XNodescript,Class<?>parameterType)->parseScriptNode()->parseDynamicTags(context)

最后经过parseDynamicTags(context)方法解析select、insert、update、delete标签内容将结果保留在MixedSqlNode对象中的SqlNode汇合中。

publicclassMixedSqlNodeimplementsSqlNode{privatefinalList<SqlNode>contents;publicMixedSqlNode(List<SqlNode>contents){this.contents=contents;}@Overridepublicbooleanapply(DynamicContextcontext){contents.forEach(node->node.apply(context));returntrue;}}

SqlNode是一个接口,有10个成功类如下:

源码解读

可以看出咱们的select、insert、update、delete标签中蕴含的各个文本(蕴含占位符#{}和${})、子标签都有对应的SqlNode成功类,后续运转中,Mybatis关于select、insert、update、delete标签的sql语句处置都与这里的SqlNode各个成功类关系。自此咱们mybatis-spring初始化流程中关系的关键代码都过了一遍。

二、运转中,sql语句占位符#{}和${}的处置

这里间接给出xml文件查问方法标签内容。

<select>select<include/>fromtb_newbee_mall_order<where><if'">andorder_no=#{orderNo}</if><if'">anduser_id=#{userId}</if><if'">andpay_type=#{payType}</if><if'">andorder_status=#{orderStatus}</if><if'">andis_deleted=#{isDeleted}</if><if'">andcreate_time>#{startTime}</if><if'">andcreate_time<#{endTime}</if></where><if>orderby${sortField}${order}</if><if>limit#{start},#{limit}</if></select>

运转时Mybatis灵活代理MapperProxy对象的调用流程,如下:

->newBeeMallOrderMapper.findNewBeeMallOrderList(pageUtil);->MapperProxy.invoke(Objectproxy,Methodmethod,Object[]args)->MapperProxy.invoke(Objectproxy,Methodmethod,Object[]args,SqlSessionsqlSession)->MapperMethod.execute(SqlSessionsqlSession,Object[]args)->MapperMethod.executeForMany(SqlSessionsqlSession,Object[]args)->SqlSessionTemplate.selectList(Stringstatement,Objectparameter)->SqlSessionInterceptor.invoke(Objectproxy,Methodmethod,Object[]args)->DefaultSqlSession.selectList(Stringstatement,Objectparameter)->DefaultSqlSession.selectList(Stringstatement,Objectparameter,RowBoundsrowBounds)->DefaultSqlSession.selectList(Stringstatement,Objectparameter,RowBoundsrowBounds,ResultHandlerhandler)->CachingExecutor.query(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,ResultHandlerresultHandler)->MappedStatement.getBoundSql(ObjectparameterObject)->DynamicSqlSource.getBoundSql(ObjectparameterObject)->MixedSqlNode.apply(DynamicContextcontext)//${}占位符处置->SqlSourceBuilder.parse(StringoriginalSql,Class<?>parameterType,Map<String,Object>additionalParameters)//#{}占位符处置

Mybatis经过DynamicSqlSource.getBoundSql(ObjectparameterObject)方法对select、insert、update、delete标签内容做sql转换处置,代码如下:

@OverridepublicBoundSqlgetBoundSql(ObjectparameterObject){DynamicContextcontext=newDynamicContext(configuration,parameterObject);rootSqlNode.apply(context);SqlSourceBuildersqlSourceParser=newSqlSourceBuilder(configuration);Class<?>parameterType=parameterObject==null?Object.class:parameterObject.getClass();SqlSourcesqlSource=sqlSourceParser.parse(context.getSql(),parameterType,context.getBindings());BoundSqlboundSql=sqlSource.getBoundSql(parameterObject);context.getBindings().forEach(boundSql::setAdditionalParameter);returnboundSql;}

1、${}占位符处置

在rootSqlNode.apply(context)->MixedSqlNode.apply(DynamicContextcontext)中会将SqlNode汇合拼接成实践要口头的sql语句保留在DynamicContext对象中。这里给出SqlNode汇合的调试截图。

可以看出咱们的${}占位符文本的SqlNode成功类为TextSqlNode,apply方法关系操作如下:

publicclassTextSqlNodeimplementsSqlNode{...@Overridepublicbooleanapply(DynamicContextcontext){GenericTokenParserparser=createParser(newBindingTokenParser(context,injectionFilter));context.appendSql(parser.parse(text));returntrue;}privateGenericTokenParsercreateParser(TokenHandlerhandler){returnnewGenericTokenParser("${","}",handler);}//划重点,${}占位符交流逻辑在就handleToken(Stringcontent)方法中@OverridepublicStringhandleToken(Stringcontent){Objectparameter=context.getBindings().get("_parameter");if(parameter==null){context.getBindings().put("value",null);}elseif(SimpleTypeRegistry.isSimpleType(parameter.getClass())){context.getBindings().put("value",parameter);}Objectvalue=OgnlCache.getValue(content,context.getBindings());StringsrtValue=value==null?"":String.valueOf(value);//issue#274return""insteadof"null"checkInjection(srtValue);returnsrtValue;}}publicclassGenericTokenParser{publicStringparse(Stringtext){...do{...if(end==-1){...}else{builder.append(handler.handleToken(expression.toString()));offset=end+closeToken.length();}}...}while(start>-1);...returnbuilder.toString();}}

划重点,${}占位符处置如下:

handleToken(Stringcontent)方法中,Mybatis会经过ognl表白式将${}的结果间接拼接在sql语句中,由此咱们得悉${}占位符拼接的字段就是咱们传入的原样字段,有着Sql注入危险

2、#{}占位符处置

#{}占位符文本的SqlNode成功类为StaticTextSqlNode,检查源码。

publicclassStaticTextSqlNodeimplementsSqlNode{privatefinalStringtext;publicStaticTextSqlNode(Stringtext){this.text=text;}@Overridepublicbooleanapply(DynamicContextcontext){context.appendSql(text);returntrue;}}

StaticTextSqlNode会间接将节点内容拼接在sql语句中,也就是说在rootSqlNode.apply(context)方法口头终了后,此时的sql语句如下:

selectorder_id,order_no,user_id,total_price,pay_status,pay_type,pay_time,order_status,extra_info,user_name,user_phone,user_address,is_deleted,create_time,update_timefromtb_newbee_mall_orderorderbycreate_timedesclimit#{start},#{limit}

Mybatis会经过下面提到getBoundSql(ObjectparameterObject)方法中的。

sqlSourceParser.parse()方法成功#{}占位符的处置,代码如下:

publicSqlSourceparse(StringoriginalSql,Class<?>parameterType,Map<String,Object>additionalParameters){ParameterMappingTokenHandlerhandler=newParameterMappingTokenHandler(configuration,parameterType,additionalParameters);GenericTokenParserparser=newGenericTokenParser("#{","}",handler);Stringsql;if(configuration.isShrinkWhitespacesInSql()){sql=parser.parse(removeExtraWhitespaces(originalSql));}else{sql=parser.parse(originalSql);}returnnewStaticSqlSource(configuration,sql,handler.getParameterMappings());}

看到了相熟的#{占位符没有,哈哈,Mybatis关于#{}占位符的处置就在GenericTokenParser类的parse()方法中,代码如下:

publicclassGenericTokenParser{publicStringparse(Stringtext){...do{...if(end==-1){...}else{builder.append(handler.handleToken(expression.toString()));offset=end+closeToken.length();}}...}while(start>-1);...returnbuilder.toString();}}publicclassSqlSourceBuilderextendsBaseBuilder{...//划重点,#{}占位符交流逻辑在就SqlSourceBuilder.handleToken(Stringcontent)方法中@OverridepublicStringhandleToken(Stringcontent){parameterMappings.add(buildParameterMapping(content));return"?";}}

划重点,#{}占位符处置如下:

handleToken(Stringcontent)方法中,Mybatis会间接将咱们的传入参数转换成问号(就是jdbc规范中的问号),也就是说咱们的sql语句是预处置的。能够防止sql注入疑问

三.总结

由上经过源码剖析,咱们知道Mybatis对#{}占位符是间接转换成问号,拼接预处置sql。${}占位符是原样拼接处置,有sql注入危险,最好防止由客户端传入此参数。


mybatis $和#的区别?

都是用于取值。

#是?占位符代替参数,预编译,防止sql注入,想想jdbc的PreparedStatement;

$是直接字符串替换。不推荐使用,有sql注入风险,想想jdbc的Statement

例子

如果满意,望采纳,谢谢。

mybatis中$和#的区别是什么

免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。

标签: Mybatis