版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LHQJ1992/article/details/90320639
SqlSource
构建动态SQL
//XMLStatementBuilder.parseStatementNode() SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
涉及类
- LanguageDriverRegistry+LanguageDriver
- XMLStatementBuilder
- SqlSource+SqlNode+SqlSourceBuilder
- MappedStatement
0、 解析Statement中一些重要点
- SqlSource如何构建?
- Sql语句如何和参数拼接成完整的可执行sql?
1、LanguageDriverRegistry+LanguageDriver
在初始化Configuration时,会预先初始化Mybatis提供的两个LanguageDriver的实现类并注册到LanguageDriverRegistry
中:RawLanguageDriver
和XMLLanguageDriver
,默认为XMLLanguageDriver
。
- LanguageDriverRegistry中用
LANGUAGE_DRIVER_MAP
来缓存LanguageDriver- key:LanguageDriver的Class
- value:LanguageDriver的实例对象(newInstance())
1.1、XMLLanguageDriver
作用于解析select|update|insert|delete节点为完整的SQL语句,对应XML格式的配置文件,创建SqlSource、Parameterhandler
- 主要方法1:
createSqlSource(...)
:调用XMLScriptBuilder
创建SqlSource - 主要方法2:
createParameterHandler(...)
: 创建DefaultParameterHandler
1.2、RawLanguageDriver
继承自XMLLanguageDriver,RawSqlSource 语言驱动器实现类,确保创建的 SqlSource 是 RawSqlSource 类。
- 主要方法1:
createSqlSource(...)
:调用父类XMLLanguageDriver来创建SqlSource。 - 主要方法2:
checkIsNotDynamic(...)
:验证创建的SqlSource是不是RawSqlSource
2、SqlSource&SqlNode (组合设计模式)
- 创建SqlSource时序图
- SqlSource解析关联类图
public interface SqlSource { //通过SqlSource获得BoundSql BoundSql getBoundSql(Object parameterObject); }
2.1、XMLScriptBuilder
XML 动态语句( SQL )构建器,负责将 SQL 解析成 SqlSource 对象。
- 在初始化XMLScriptBuilder时,会创建很多
NodeHandler
,用于解析如where、set、foreach、if
等节点。
private void initNodeHandlerMap() { nodeHandlerMap.put("trim", new TrimHandler()); nodeHandlerMap.put("where", new WhereHandler()); nodeHandlerMap.put("set", new SetHandler()); nodeHandlerMap.put("foreach", new ForEachHandler()); nodeHandlerMap.put("if", new IfHandler()); nodeHandlerMap.put("choose", new ChooseHandler()); nodeHandlerMap.put("when", new IfHandler()); nodeHandlerMap.put("otherwise", new OtherwiseHandler()); nodeHandlerMap.put("bind", new BindHandler()); }
- 入口方法
parseScriptNode()
:负责将 SQL 解析成 SqlSource 对象。- 调用
parseDynamicTags()
方法:解析 SQL 语句节点,会判断节点是是否包含一些动态标记。再根据不同节点,通过NodeHandler的handleNode处理,最后构建出SqlNode(MixedSqlNode)
- 若为动态Sql,则创建DynamicSqlSource;非动态SQL则创建RawSqlSource
- DynamicSqlSource:只创建DynamicSqlSource对象,完整可执行的sql,需要在调用
getBoundSql(Parameter)
方法时才能组装完成。 - RawSqlSource:调用 SqlSourceBuilder,#{}符 替换为占位符?,并绑定ParameterMapping,最后返回的RawSqlSource中持有一个真正的由SqlSourceBuilder构建的
SqlSource
对象。- DynamicContext:动态 SQL ,用于每次执行 SQL 操作时,记录动态 SQL 处理后的最终 SQL 字符串。
- DynamicSqlSource:只创建DynamicSqlSource对象,完整可执行的sql,需要在调用
- 调用
- parseDynamicTags():不管是动态 SQL 节点还是静态 SQL 节点,都可以把它们看成是 SQL 片段,一个 SQL 语句由多个 SQL 片段组成,会将这些片段存放在contents中,最后用于创建MixedSqlNode实例。
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); //NOTE: 遍历子节点 NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); //NOTE: 分支1 if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { //NOTE: 获取文本 String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); //NOTE: 判断是否为动态sql,若其中包含"${}",则表示为动态sql if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } //NOTE: child 节点是 ELEMENT_NODE 类型,比如 <if>、<where> 等 else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNode().getNodeName(); //NOTE: 获取对应的NodeHandler,生成相应的 SqlNode NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } //NOTE: 将各种sql片段保存到MixedSqlNode中 return new MixedSqlNode(contents); }
- SqlNode是如何解析的?
2.2、SqlSource
SQL 来源接口,它代表从 Mapper XML 或方法注解上,读取的一条 SQL 内容。
2.2.1、 非动态Sql,创建RawSqlSource对象
- 首先获取sql语句
private static String getSql(Configuration configuration, SqlNode rootSqlNode) { DynamicContext context = new DynamicContext(configuration, null); rootSqlNode.apply(context); return context.getSql(); }
- 调用 DynamicContext:用于每次执行 SQL 操作时,记录动态 SQL 处理后的最终 SQL 字符串。
- 将getSql()得到的originalSql,再通过
SqlSourceBuilder
构建StaticSqlSource
对象。
2.2.2、 动态Sql,创建DynamicSqlSource对象
- DynamicSqlSource在真正发起语句执行时,才会调SqlSourceBuilder来进行参数拼装。
2.3、SqlSourceBuilder:TODO
继承 BaseBuilder 抽象类,SqlSource的构建器,负责将 SQL 语句中的 #{} 替换成相应的 ? 占位符,并获取该 ? 占位符对应的ParameterMapping 对象。
- 入口方法
parse()
:执行解析原始 SQL ,关联ParameterMapping,构建 SqlSource 对象,标记是否为动态SQL? - 具体解析originalSql的过程在ParameterMappingTokenHandler中:TODO
- 最后返回
StaticSqlSource
对象:封装SQL、configuration和ParameterMapping
2.4、DynamicContext
用于每次执行 SQL 操作时,记录动态 SQL 处理后的最终 SQL 字符串。
- 设置 OGNL的属性访问器,实现ContextMap和ContextAccessor
- 用
StringJoiner sqlBuilder = new StringJoiner(" ")
记录Sql,其实就是将之前解析的多个Sql片段进行合并。 - 在每个SqlNode实现类中,会调用每个SqlNode的
apply(DynamicContext context)
方法,将SQL添加到StringJoiner中。 - DynamicContext中有一个内部类
ContextMap
用于记录传入的参数。若传入的参数不是Map类型时,会创建对应的MetaObject对象,并封装成ContextMap对象。
3、 MappedStatement
该类中有个核心方法:getBoundSql()
其中sqlSource为MappedStatement对象对应的sql语句信息封装,在后续真正执行SQL时,会调用改方法来绑定具体的参数值。
public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }
文章来源: https://blog.csdn.net/LHQJ1992/article/details/90320639