这个问题也是面试中常被问到的,就抽空来了解下这个,跳过一大段前面初始化的逻辑, 对于一条select * from t1 where id = #{id}
这样的 sql,在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource,这里它是一条 RawSqlSource, 在这里做了替换,将#{}
替换成了?
前面说的是否 dynamic 就是在这里进行判断1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public SqlSource parseScriptNode () { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource (configuration, rootSqlNode); } else { sqlSource = new RawSqlSource (configuration, rootSqlNode, parameterType); } return sqlSource; } protected MixedSqlNode parseDynamicTags (XNode node) { List<SqlNode> contents = new ArrayList <>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0 ; i < children.getLength(); i++) { 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) { String 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); } public boolean isDynamic () { DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser (); GenericTokenParser parser = createParser(checker); parser.parse(text); return checker.isDynamic(); } private GenericTokenParser createParser (TokenHandler handler) { return new GenericTokenParser ("${" , "}" , handler); }
可以看到其中一个条件就是是否有${}
这种占位符,假如说上面的 sql 换成 ${}
,那么可以看到它会在这里创建一个 dynamicSqlSource,1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class DynamicSqlSource implements SqlSource { private final Configuration configuration; private final SqlNode rootSqlNode; public DynamicSqlSource (Configuration configuration, SqlNode rootSqlNode) { this .configuration = configuration; this .rootSqlNode = rootSqlNode; } @Override public BoundSql getBoundSql (Object 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; } }
这里眼尖的同学可能就可以看出来了,RawSqlSource 在初始化的时候已经经过了 parse,把#{}
替换成了?
占位符,但是 DynamicSqlSource 并没有 再看这个图,我们发现在这的时候还没有进行替换 然后往里跟 好像是这里了 这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用,不同类型的标签会构成不同的 node,像这里就是一个 textSqlNode 可以发现到这我们的 sql 已经被替换了,而且是直接作为 string 类型替换的,所以可以明白了这个问题所在,就是注入,不过细心的同学发现其实这里是有个 理论上还是可以做过滤的,不过好像现在没用起来。 我们前面可以发现对于#{}
是在启动扫描 mapper的 xml 文件就替换成了 ?
,然后是在什么时候变成实际的值的呢 发现到这的时候还是没有替换,其实说白了也就是 prepareStatement 那一套, 在这里进行替换,会拿到 org.apache.ibatis.mapping.ParameterMapping,然后进行替换,因为会带着类型信息,所以不用担心注入咯