1. 斐波那契数列的数学魅力
斐波那契数列这个数学概念最早出现在印度数学中,后来由意大利数学家斐波那契引入西方。这个数列看似简单,却蕴含着惊人的数学规律和美学价值。数列从0和1开始,后续每个数字都是前两个数字之和,形成0,1,1,2,3,5,8,13...这样无限延伸的序列。
在实际编程中,计算斐波那契数列第n个数是个经典问题,它不仅能帮助我们理解递归和迭代的思想,还能让我们深入掌握算法优化的技巧。对于初学者来说,这是理解基础算法的最佳切入点;对于有经验的开发者,这又是检验算法优化能力的试金石。
2. 递归解法:最直观的实现方式
2.1 基础递归实现
最直观的解法就是直接按照斐波那契数列的定义来实现递归函数:
def fibonacci_recursive(n): if n <= 1: return n return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)这个实现简洁明了,完美体现了数学定义。当n为0或1时直接返回n,否则返回前两项之和。这种方法的优点是代码极其简单,几乎就是数学定义的直接翻译。
2.2 递归解法的性能问题
然而,这种朴素的递归实现存在严重的性能缺陷。计算fibonacci_recursive(5)时,函数调用树会像这样展开:
fib(5) ├── fib(4) │ ├── fib(3) │ │ ├── fib(2) │ │ │ ├── fib(1) │ │ │ └── fib(0) │ │ └── fib(1) │ └── fib(2) │ ├── fib(1) │ └── fib(0) └── fib(3) ├── fib(2) │ ├── fib(1) │ └── fib(0) └── fib(1)可以看到,fib(3)被计算了2次,fib(2)被计算了3次,存在大量重复计算。时间复杂度是指数级的O(2^n),空间复杂度是O(n)(由于递归调用栈)。
注意:在实际应用中,这种朴素递归方法仅适用于n很小的情况(n<30)。对于更大的n,计算时间会变得不可接受。
3. 优化方案一:记忆化递归
3.1 引入缓存机制
为了优化递归解法,我们可以引入记忆化技术,将已经计算过的结果存储起来,避免重复计算:
def fibonacci_memo(n, memo={}): if n <= 1: return n if n not in memo: memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo) return memo[n]这种方法使用一个字典memo来缓存计算结果。每次计算前先检查是否已经缓存,如果已经缓存就直接返回结果,否则进行计算并缓存。
3.2 性能分析
记忆化技术将时间复杂度从O(2^n)降低到了O(n),因为每个fib(i)只需要计算一次。空间复杂度也是O(n),因为需要存储n个计算结果。
虽然性能大幅提升,但这种实现仍然使用递归,当n很大时(比如n>1000),可能会遇到递归深度限制的问题,导致栈溢出。
4. 优化方案二:迭代解法
4.1 基础迭代实现
为了避免递归带来的问题,我们可以改用迭代方式实现:
def fibonacci_iterative(n): if n <= 1: return n a, b = 0, 1 for _ in range(2, n+1): a, b = b, a + b return b这种方法从底部开始计算,依次求出fib(2), fib(3), ..., fib(n)。它只需要常数级别的额外空间(存储前两个值),时间复杂度是O(n),空间复杂度是O(1)。
4.2 迭代解法的优势
迭代解法完全避免了递归带来的栈溢出风险,可以处理非常大的n值# 1. 概述
本文,我们来分享 MyBatis 的脚本模块,对应scripting包。如下图所示:scripting包
在 《MyBatis 文档 —— 动态 SQL》 中,我们已经了解到,MyBatis 强大的动态 SQL 功能,可以根据不同的条件,来拼接不同的 SQL 。而动态 SQL 的实现原理,就是基于scripting模块,根据我们编写的动态 SQL 的代码,生成对应的可执行 SQL。
下面,我们就来看看具体的源码。因为scripting是 MyBatis 非常底层的一个模块,所以我们会从最基础的脚本功能开始看起。
2. LanguageDriver
org.apache.ibatis.scripting.LanguageDriver,语言驱动接口。代码如下:
// LanguageDriver.java public interface LanguageDriver { /** * Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement. * * 创建 ParameterHandler 对象。 * * @param mappedStatement The mapped statement that is being executed * @param parameterObject The input parameter object (can be null) * @param boundSql The resulting SQL once the dynamic language has been executed. * @return the parameter handler * @see DefaultParameterHandler */ ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); /** * Creates an {@link SqlSource} that will hold the statement read from a mapper xml file. * It is called during startup, when the mapped statement is read from a class or an xml file. * * 创建 SqlSource 对象,从 Mapper XML 配置的 <select /> 等标签中,读取内容。 * * @param configuration The MyBatis configuration * @param script XNode parsed from a XML file * @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null. * @return the sql source */ SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType); /** * Creates an {@link SqlSource} that will hold the statement read from an annotation. * It is called during startup, when the mapped statement is read from a class or an xml file. * * 创建 SqlSource 对象,从方法注解配置中,读取内容。 * * @param configuration The MyBatis configuration * @param script The content of the annotation * @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null. * @return the sql source */ SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType); }- 定义了创建 ParameterHandler 和 SqlSource 对象的方法。
2.1 XMLLanguageDriver
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver,实现 LanguageDriver 接口,XML 语言驱动实现类。
2.1.1 createSqlSource
#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)方法,代码如下:
// XMLLanguageDriver.java @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { // <1> 创建 XMLScriptBuilder 对象,执行解析 XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }<1>处,创建 XMLScriptBuilder 对象,执行XMLScriptBuilder#parseScriptNode()方法,执行解析。关于 XMLScriptBuilder 类,详细解析,见 「3.1 XMLScriptBuilder」 。
#createSqlSource(Configuration configuration, String script, Class<?> parameterType)方法,代码如下:
// XMLLanguageDriver.java @Override public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { // <1> 如果是 <script> 开头,使用 XML 配置的方式,使用动态 SQL if (script.startsWith("<script>")) { // <1.1> 创建 XPathParser 对象,解析出 <script /> 节点 XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver()); // <1.2> 调用上面的 #createSqlSource(...) 方法,创建 SqlSource 对象 return createSqlSource(configuration, parser.evalNode("/script"), parameterType); // <2> } else { // <2.1> 变量替换 script = PropertyParser.parse(script, configuration.getVariables()); // <2.2> 创建 TextSqlNode 对象 TextSqlNode textSqlNode = new TextSqlNode(script); // <2.3> 如果是动态 SQL ,则创建 DynamicSqlSource 对象 if (textSqlNode.isDynamic()) { return new DynamicSqlSource(configuration, textSqlNode); // <2.4> 如果非动态 SQL ,则创建 RawSqlSource 对象 } else { return new RawSqlSource(configuration, script, parameterType); } } }<1>处,如果是<script>开头,使用 XML 配置的方式,使用动态 SQL 。<1.1>处,创建 XPathParser 对象,解析出<script />节点。<1.2>处,调用#createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)方法,创建 SqlSource 对象。
<2>处,如果是非<script>开头,使用注解的方式,可能使用动态 SQL 。<2.1>处,变量替换。<2.2>处,创建 TextSqlNode 对象。<2.3>处,如果是动态 SQL ,则创建 DynamicSqlSource 对象。<2.4>处,如果非动态 SQL ,则创建 RawSqlSource 对象。
2.1.2 createParameterHandler
#createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql)方法,代码如下:
// XMLLanguageDriver.java @Override public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { // 创建 DefaultParameterHandler 对象 return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); }- 直接创建 DefaultParameterHandler 对象。
2.2 RawLanguageDriver
org.apache.ibatis.scripting.defaults.RawLanguageDriver,继承 XMLLanguageDriver 类,Raw语言驱动实现类,确保创建的 SqlSource 是RawSqlSource类。代码如下:
// RawLanguageDriver.java public class RawLanguageDriver extends XMLLanguageDriver { @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { // 调用父类,创建 SqlSource 对象 SqlSource source = super.createSqlSource(configuration, script, parameterType); // 校验创建的是 RawSqlSource 对象 checkIsNotDynamic(source); return source; } @Override public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { // 调用父类,创建 SqlSource 对象 SqlSource source = super.createSqlSource(configuration, script, parameterType); // 校验创建的是 RawSqlSource 对象 checkIsNotDynamic(source); return source; } /** * 校验是 RawSqlSource 对象 * * @param source 创建的 SqlSource 对象 */ private void checkIsNotDynamic(SqlSource source) { if (!RawSqlSource.class.equals(source.getClass())) { throw new BuilderException("Dynamic content is not allowed when using RAW language"); } } }该类目前仅仅在单元测试中使用。例如:
// RawLanguageDriverTest.java @Test public void testRawLanguageWithDynamicContent() { try { String sql = "SELECT * FROM table WHERE id = #{value}"; LanguageDriver langDriver = new RawLanguageDriver(); langDriver.createSqlSource(configuration, sql, null); Assert.fail("Raw language driver should not be able to parse dynamic SQL"); } catch (BuilderException ex) { Assert.assertTrue(ex.getMessage().contains("Dynamic content is not allowed when using RAW language")); } }
2.3 LanguageDriverRegistry
org.apache.ibatis.scripting.LanguageDriverRegistry,LanguageDriver 注册表。代码如下:
// LanguageDriverRegistry.java public class LanguageDriverRegistry { /** * LanguageDriver 映射 */ private final Map<Class<? extends LanguageDriver>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>(); /** * 默认的 LanguageDriver 类 */ private Class<? extends LanguageDriver> defaultDriverClass; public void register(Class<? extends LanguageDriver> cls) { if (cls == null) { throw new IllegalArgumentException("null is not a valid Language Driver"); } // 创建 cls 对应的对象,并添加到 LANGUAGE_DRIVER_MAP 中 if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) { try { LANGUAGE_DRIVER_MAP.put(cls, cls.newInstance()); } catch (Exception ex) { throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex); } } } public void register(LanguageDriver instance) { if (instance == null) { throw new IllegalArgumentException("null is not a valid Language Driver"); } // 添加到 LANGUAGE_DRIVER_MAP 中 Class<? extends LanguageDriver> cls = instance.getClass(); if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) { LANGUAGE_DRIVER_MAP.put(cls, instance); } } public LanguageDriver getDriver(Class<? extends LanguageDriver> cls) { return LANGUAGE_DRIVER_MAP.get(cls); } public LanguageDriver getDefaultDriver() { return getDriver(getDefaultDriverClass()); } public Class<? extends LanguageDriver> getDefaultDriverClass() { return defaultDriverClass; } /** * 设置 {@link #defaultDriverClass} * * @param defaultDriverClass 默认的 LanguageDriver 类 */ public void setDefaultDriverClass(Class<? extends LanguageDriver> defaultDriverClass) { // 注册到 LANGUAGE_DRIVER_MAP 中 register(defaultDriverClass); // 设置 defaultDriverClass this.defaultDriverClass = defaultDriverClass; } }- 比较简单,胖友自己看看。
2.4 DefaultScriptingLanguageDriver
在 Configuration 的构造方法中,会进行初始化。代码如下:
// Configuration.java /** * LanguageDriverRegistry 对象 */ protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); public Configuration() { // ... 省略其它代码 // 注册默认的 LanguageDriver 类 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); }- 默认情况下,使用 XMLLanguageDriver 类。
3. XML 动态 SQL
在scripting包中,xmltags包负责处理 XML 动态 SQL 相关的内容。而动态 SQL 相关的内容,在 《MyBatis 文档 —— 动态 SQL》 中,已经有详细的介绍。所以,本小节,更多的是从代码的角度,来看看动态 SQL 是如何实现的。
3.1 XMLScriptBuilder
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder,继承 BaseBuilder 抽象类,XML 动态语句( SQL )构建器,负责将 SQL 解析成 SqlSource 对象。
3.1.1 构造方法
// XMLScriptBuilder.java /** * 当前 SQL 的 XNode 对象 */ private final XNode context; /** * 是否为动态 SQL */ private boolean isDynamic; /** * SQL 方法类型 */ private final Class<?> parameterType; /** * NodeNodeHandler 的映射 */ private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>(); public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.context = context; this.parameterType = parameterType; // 初始化 nodeHandlerMap 属性 initNodeHandlerMap(); }context属性,当前 SQL 的 XNode 对象。例如:<select id="getUser" resultType="User">SELECT * FROM user WHERE id = #{id}</select>。isDynamic属性,是否为动态 SQL 。parameterType属性,SQL 方法类型。nodeHandlerMap属性,NodeNodeHandler 的映射。在#initNodeHandlerMap()方法中,我们可以看到如下代码:
// XMLScriptBuilder.java 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()); }- 我们可以看到,
nodeHandlerMap的 KEY 是熟悉的 MyBatis 动态 SQL 的节点。并且,每个节点对应专属的一个 NodeHandler 实现类。关于 NodeHandler 类,在 「3.2 NodeHandler」 中,详细解析。
3.1.2 parseScriptNode
#parseScriptNode()方法,负责解析 SQL 节点,生成对应的 SqlSource 对象。代码如下:
// XMLScriptBuilder.java public SqlSource parseScriptNode() { // <1> 解析 SQL MixedSqlNode rootSqlNode = parseDynamicTags(context); // <2> 创建 SqlSource 对象 SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }<1>处,调用#parseDynamicTags(XNode node)方法,解析 SQL 成 MixedSqlNode 对象。详细解析,见 「3.1.3 parseDynamicTags」 。<2>处,判断是否为动态 SQL 。如果是,则创建 DynamicSqlSource 对象。如果否,则创建 RawSqlSource 对象。
3.1.3 parseDynamicTags
#parseDynamicTags(XNode node)方法,解析 SQL 成 MixedSqlNode 对象。代码如下:
// XMLScriptBuilder.java protected MixedSqlNode parseDynamicTags(XNode node) { // <1> 创建 SqlNode 数组 List<SqlNode> contents = new ArrayList<>(); // <2> 遍历 SQL 节点的所有子节点 NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { // 当前子节点 XNode child = node.newXNode(children.item(i)); // <3> 如果类型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 时 if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // <3.1> 获得内容 String data = child.getStringBody(""); // <3.2> 创建 TextSqlNode 对象 TextSqlNode textSqlNode = new TextSqlNode(data); // 如果是动态的 TextSqlNode 对象 if (textSqlNode.isDynamic()) { // 添加到 contents 中 contents.add(textSqlNode); // 标记为动态 SQL isDynamic = true; // 如果是非动态的 TextSqlNode 对象 } else { // <3.3> 创建 StaticTextSqlNode 添加到 contents 中 contents.add(new StaticTextSqlNode(data)); } // <4> 如果类型是 Node.ELEMENT_NODE } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 // <4.1> 根据子节点的标签,获得对应的 NodeHandler 对象 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } // <4.2> 执行 NodeHandler 处理 handler.handleNode(child, contents); // <4.3> 标记为动态 SQL isDynamic = true; } } // <5> 创建 MixedSqlNode 对象 return new MixedSqlNode(contents); }<1>处,创建 SqlNode 数组contents,用于存储解析到的 SqlNode 节点们。<2>处,遍历 SQL 节点的所有子节点。<3>处,如果类型是Node.CDATA_SECTION_NODE或者Node.TEXT_NODE时:<3.1>处,获得节点的内容data。<3.2>处,创建 TextSqlNode 对象textSqlNode。通过调用TextSqlNode#isDynamic()方法,判断是否为动态 SQL 。如果是,则添加到contents中,并标记为动态 SQL 。否则:<3.3>处,创建 StaticTextSqlNode 添加到contents中。
<4>处,如果类型是Node.ELEMENT_NODE时:<4.1>处,根据子节点的标签,获得对应的 NodeHandler 对象。<4.2>处,执行 NodeHandler 处理。<4.3>处,标记为动态 SQL 。
<5>处,所有解析到的 SqlNode 节点们,创建成 MixedSqlNode 对象。
3.2 NodeHandler
NodeHandler ,在 XMLScriptBuilder 类中,NodeHandler 是接口,每个 XML 标签对应一个实现类。代码如下:
// XMLScriptBuilder.java private interface NodeHandler { /** * 处理 Node * * @param nodeToHandle 要处理的 XNode 节点 * @param targetContents 目标的 SqlNode 数组。实际上,被处理的 XNode 节点会创建成对应的 SqlNode 对象,添加到 targetContents 中 */ void handleNode(XNode nodeToHandle, List<SqlNode> targetContents); }- 一共有 9 个实现类,一一对应 9 个标签。如下图所示:
NodeHandler 实现类
3.2.1 BindHandler
BindHandler ,实现 NodeHandler 接口,<bind />标签的处理器。代码如下:
// XMLScriptBuilder.java private class BindHandler implements NodeHandler { public BindHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 解析 name、value 属性 final String name = nodeToHandle.getStringAttribute("name"); final String expression = nodeToHandle.getStringAttribute("value"); // 创建 VarDeclSqlNode 对象 final VarDeclSqlNode node = new VarDeclSqlNode(name, expression); // 添加到 targetContents 中 targetContents.add(node); } }- 解析
name、value属性,并创建 VarDeclSqlNode 对象,最后添加到targetContents中。
3.2.2 TrimHandler
TrimHandler ,实现 NodeHandler 接口,<trim />标签的处理器。代码如下:
// XMLScriptBuilder.java private class TrimHandler implements NodeHandler { public TrimHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // <1> 解析内部的 SQL 节点,成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // <2> 获得 prefix、prefixOverrides、suffix、suffixOverrides 属性 String prefix = nodeToHandle.getStringAttribute("prefix"); String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides"); String suffix = nodeToHandle.getStringAttribute("suffix"); String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides"); // <3> 创建 TrimSqlNode 对象 TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides); // <4> 添加到 targetContents 中 targetContents.add(trim); } }<1>处,调用#parseDynamicTags(XNode node)方法,解析内部的 SQL 节点,成 MixedSqlNode 对象。<2>处,获得prefix、prefixOverrides、suffix、suffixOverrides属性。<3>处,创建 TrimSqlNode 对象。<4>处,添加到targetContents中。
3.2.3 WhereHandler
WhereHandler ,实现 NodeHandler 接口,<where />标签的处理器。代码如下:
// XMLScriptBuilder.java private class WhereHandler implements NodeHandler { public WhereHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 解析内部的 SQL 节点,成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 创建 WhereSqlNode 对象 WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode); // 添加到 targetContents 中 targetContents.add(where); } }- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。不过,WhereHandler 创建的 WhereSqlNode 对象,是 TrimSqlNode 的简化版,两者可以对比下。
3.2.4 SetHandler
SetHandler ,实现 NodeHandler 接口,<set />标签的处理器。代码如下:
// XMLScriptBuilder.java private class SetHandler implements NodeHandler { public SetHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 解析内部的 SQL 节点,成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 创建 SetSqlNode 对象 SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode); // 添加到 targetContents 中 targetContents.add(set); } }- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。不过,SetHandler 创建的 SetSqlNode 对象,是 TrimSqlNode 的简化版,两者可以对比下。
3.2.5 ForEachHandler
ForEachHandler ,实现 NodeHandler 接口,<foreach />标签的处理器。代码如下:
// XMLScriptBuilder.java private class ForEachHandler implements NodeHandler { public ForEachHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 解析内部的 SQL 节点,成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 获得 collection、item、index、open、close、separator 属性 String collection = nodeToHandle.getStringAttribute("collection"); String item = nodeToHandle.getStringAttribute("item"); String index = nodeToHandle.getStringAttribute("index"); String open = nodeToHandle.getStringAttribute("open"); String close = nodeToHandle.getStringAttribute("close"); String separator = nodeToHandle.getStringAttribute("separator"); // 创建 ForEachSqlNode 对象 ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator); // 添加到 targetContents 中 targetContents.add(forEachSqlNode); } }- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。当然,也有差异点。例如,ForEachHandler 创建的 ForEachSqlNode 对象。
3.2.6 IfHandler
IfHandler ,实现 NodeHandler 接口,<if />标签的处理器。代码如下:
// XMLScriptBuilder.java private class IfHandler implements NodeHandler { public IfHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 解析内部的 SQL 节点,成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 获得 test 属性 String test = nodeToHandle.getStringAttribute("test"); // 创建 IfSqlNode 对象 IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); // 添加到 targetContents 中 targetContents.add(ifSqlNode); } }- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。当然,也有差异点。例如,IfHandler 创建的 IfSqlNode 对象。
3.2.7 OtherwiseHandler
OtherwiseHandler ,实现 NodeHandler 接口,<otherwise />标签的处理器。代码如下:
// XMLScriptBuilder.java private class OtherwiseHandler implements NodeHandler { public OtherwiseHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 解析内部的 SQL 节点,成 MixedSqlNode 对象 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 添加到 targetContents 中 targetContents.add(mixedSqlNode); } }- 从实现逻辑的思路上,和 TrimHandler 是一个套路的。不过,OtherwiseHandler 是直接使用 MixedSqlNode 对象。
3.2.8 ChooseHandler
ChooseHandler ,实现 NodeHandler 接口,<choose />标签的处理器。代码如下:
// XMLScriptBuilder.java private class ChooseHandler implements NodeHandler { public ChooseHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // <1> 获得 <when /> 和 <otherwise /> 的节点们 List<SqlNode> whenSqlNodes = new ArrayList<>(); List<SqlNode> otherwiseSqlNodes = new ArrayList<>(); handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes); // <2> 获得 <otherwise /> 的节点 SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes); // <3> 创建 ChooseSqlNode 对象 ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode); // <4> 添加到 targetContents 中 targetContents.add(chooseSqlNode); } private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) { List<XNode> children = chooseSqlNode.getChildren(); for (XNode child : children) { String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler instanceof IfHandler) { handler.handleNode(child, ifSqlNodes); } else if (handler instanceof OtherwiseHandler) { handler.handleNode(child, defaultSqlNodes); } } } private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) { SqlNode defaultSqlNode = null; if (defaultSqlNodes.size() == 1) { defaultSqlNode = defaultSqlNodes.get(0); } else if (defaultSqlNodes.size() > 1) { throw new BuilderException("Too many default (otherwise) elements in choose statement."); } return defaultSqlNode; } }<1>处,调用#handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes)方法,获得<when />和<otherwise />的节点们。<2>处,调用#getDefaultSqlNode(List<SqlNode> defaultSqlNodes)方法,获得<otherwise />的节点。<3>处,创建 ChooseSqlNode 对象。<4>处,添加到targetContents中。
3.3 SqlNode
org.apache.ibatis.scripting.xmltags.SqlNode,SQL Node 接口,每个 XML Node 会解析成对应的 SQL Node 对象。代码如下:
// SqlNode.java public interface SqlNode { /** * 应用当前 SQL Node 节点 * * @param context 上下文 * @return 当前 SQL Node 节点是否应用成功。 */ boolean apply(DynamicContext context); }- 关于 DynamicContext 类,在 「4.1 DynamicContext」 中详细解析。
3.3.1 VarDeclSqlNode
org.apache.ibatis.scripting.xmltags.VarDeclSqlNode,实现 SqlNode 接口,<bind />标签的 SqlNode 实现类。代码如下:
// VarDeclSqlNode.java public class VarDeclSqlNode implements SqlNode { /** * 名字 */ private final String name; /** * 表达式 */ private final String expression; public VarDeclSqlNode(String var, String exp) { name = var; expression = exp; } @Override public boolean apply(DynamicContext context) { // <1> 获得值 final Object value = OgnlCache.getValue(expression, context.getBindings()); // <2> 绑定到上下文 context.bind(name, value); return true; } }<1>