Mybatis占位符#和$的区别?源码解读

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

  • • Mybatis 版本 3.5.11
  • • Spring boot 版本 3.0.2
  • • mybatis-spring 版本 3.0.1
  • • Github地址:https://github.com/wayn111,欢迎大家关注,点个star
一. 启动时,mybatis-spring解析xml文件流程图Spring项目启动时,mybatis-spring自动初始化解析xml文件核心流程 。
Mybatis占位符#和$的区别?源码解读

文章插图
流程图
Mybatis在buildSqlSessionFactory()会遍历所有mApperLocations(xml文件)调用xmlMapperBuilder.parse()解析 , 源码如下:
Mybatis占位符#和$的区别?源码解读

文章插图
在 parse() 方法中,Mybatis通过configurationElement(parser.evalNode("/mapper"))方法解析xml文件中的各个标签 。
public class XMLMapperBuilder extends BaseBuilder {...private final MapperBuilderAssistant builderAssistant;private final Map<String, XNode> sqlFragments;...public void parse() {if (!configuration.isResourceLoaded(resource)) {// xml文件解析逻辑configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}private void configurationElement(XNode context) {try {// 解析xml文件内的namespace、cache-ref、cache、parameterMap、resultMap、sql、select、insert、update、delete等各种标签String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}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 (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}}最后会把 namespace、cache-ref、cache、parameterMap、resultMap、select、insert、update、delete等标签内容解析结果放到 builderAssistant 对象中,将sql标签解析结果放到sqlFragments对象中 , 其中 由于 builderAssistant 对象会保存select、insert、update、delete标签内容解析结果我们对 builderAssistant 对象进行深入了解 。
public class MapperBuilderAssistant extends BaseBuilder {...}public abstract class BaseBuilder {protected final Configuration configuration;...}public class Configuration {...protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) ->". please check " + savedValue.getResource() + " and " + targetValue.getResource());protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");protected final Set<String> loadedResources = new HashSet<>();protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");...}builderAssistant 对象继承至 BaseBuilder,BaseBuilder 类中包含一个 configuration 对象属性,configuration 对象中会保存xml文件标签解析结果至自身对应属性mappedStatements、caches、resultMaps、sqlFragments 。
这里有个问题上面提到的sql标签结果会放到 XMLMapperBuilder 类的 sqlFragments 对象中,为什么 Configuration 类中也有个 sqlFragments 属性?
这里回看上文buildSqlSessionFactory()方法最后 。
Mybatis占位符#和$的区别?源码解读

文章插图
原来 XMLMapperBuilder 类中的 sqlFragments 属性就来自Configuration类 。
回到主题,在 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法中会通过如下调用 。
buildStatementFromContext(List<XNode> list, String requiredDatabaseId) -> parseStatementNode()-> createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)-> parseScriptNode()-> parseDynamicTags(context)


推荐阅读