一百度${}, #{} 网上有好多答案,不过还是自己跟过源码才能理解原理
一 概述
其实${} 没什么特别的,也可以从入参为POJO里获取属性值进行替换。需要注意的是它通过ognl表达式把POJO做变更,然后从里面获取属性值,进行纯粹的字符串拼接。
所以需要注意,比如一张表有一个列是VARCHAR,如果在POJO里 setName("name") 是不够的,应该是setName("'name'"),加上单引号。
还有就是$ #是可以同时用的。我们就来看看源码吧
二 源码分析
现在我们有如下的sql语句
<insert id="insertDollar" parameterType="me.gacl.domain.User" >
insert into t_user
values (${userId}, #{userName}, ${userBirthday},
${userSalary})
</insert>
如果代码这样写是没有问题的
User user = (User) ac.getBean("user"); user.setUserId("'11'"); user.setUserName("白虎神皇xdp"); user.setUserBirthday("'123'"); user.setUserSalary(10000D); user.setTableName("t_user"); userService.insertDollar(user);
首先要说明的是,如果一个sql里包含了$,mybatis在解析的时候是当作 DynamicSqlSource 的
之前分析StatementHandler的时候我们知道,执行前要根据入参确定最终的sql语句也就是获取boundSql
我们看 DynamicSqlSource.getBoundSql
public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context);//经过了apply之后 insert into t_user values ('11', #{userName}, '123', 10000.0) 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); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }
继续分析apply,对于$,每一个sqlNode都是 TextSqlNode
@Override 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); }
parse方法之前在分析#的时候,是分析过的,就是根据输入的开始和结束符进行字符串的解析,很纯粹的字符串操作,这里面要注意的是 handler.handleToken(content)
public String parse(String text) { StringBuilder builder = new StringBuilder(); if (text != null && text.length() > 0) { char[] src = text.toCharArray(); int offset = 0; int start = text.indexOf(openToken, offset); while (start > -1) { if (start > 0 && src[start - 1] == '\') { // the variable is escaped. remove the backslash. builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { int end = text.indexOf(closeToken, start); if (end == -1) { builder.append(src, offset, src.length - offset); offset = src.length; } else { builder.append(src, offset, start - offset); offset = start + openToken.length(); String content = new String(src, offset, end - offset); builder.append(handler.handleToken(content)); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } } return builder.toString(); }
BindingTokenParser.handleToken
@Override 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());//通过ognl从对象里取值 String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" checkInjection(srtValue); return srtValue; //注意这里是String }
到此关于$的源码就分析完了,特别注意# $是可以共存的,先解析$符号部分,然后再解析#部分。