Mybatis3.4.x技术内幕(十七):Mybatis之动态Sql设计原本(上)
2017-06-04 14:31
585 查看
摘要: Mybatis的动态Sql功能,是通过XML标签结合OGNL表达式来完成的。
上一篇博文中,介绍了可复用的sql片段,通过<include>标签进行引入,而<include>标签内一般存放的是静态sql,其实,sql片段也是可以放置动态sql标签内容。
Mybatis所支持的动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。
为了避免篇幅过长,我们简单列举了几个动态sql的基本用法,我们的重点依然是剖析Mybatis动态sql的底层设计原理。
org.apache.ib
3ff0
atis.builder.xml.XMLStatementBuilder.parseStatementNode()部分源码:
(Made In IntelliJ Idea IDE)
DynamicSqlSource:处理动态sql。
RawSqlSource:处理静态sql,其内部装饰StaticSqlSource。
StaticSqlSource:处理静态sql,无论是静态sql,还是动态sql,最终的处理结果,都是静态sql。
ProviderSqlSource:处理注解Annotation形式的sql。
DynamicSqlSource和StaticSqlSource的最大区别在于:StaticSqlSource的String sql,可以直接获取使用,而DynamicSqlSource的String sql需要逐一根据条件解析并拼接出最终的sql,方能使用。
boolean apply(DynamicContext context):该方法的含义为,将sql的处理结果,append到DynamicContext context对象中,DynamicContext可以理解为StringBuilder对象的功能,它的作用就是计算sql片段并append到一起,形成最终的sql。对该方法的理解非常重要,只有理解了这个方法,才能真正明白一个完整sql是如何组装出来的。
下面的伪代码,展示了SqlNode.apply(DynamicContext)方法设计的核心原理。
DynamicSqlSource为动态sql源,而SqlNode则具体代表了动态sql源中具体的动态sql类型。
(Made In IntelliJ Idea IDE)
上面的SqlNode,基本上见名知意,我们着重解释一下容易迷惑的两个SqlNode。
VarDeclSqlNode:处理动态sql标签<bind>的SqlNode类。
MixedSqlNode:意为混合的SqlNode,它保存了其他多种SqlNode的集合,可以看做是一个List<SqlNode>列表,事实也确实如此。
DynamicSqlSource中的SqlNode rootSqlNode属性,通常都是MixedSqlNode对象(完全是静态sql时,可能是一个StaticTextSqlNode),而MixedSqlNode对象又保存了所有的List<SqlNode>集合,这也是通过一个rootSqlNode,就能找到所有SqlNode的深层原因。
SqlNode,采用了组合设计模式,组合设计模式可以用来表示经典的树型结构,有人不禁要问,组合设计模式,它的属性,应该List<SqlNode>集合,怎么会是单一的SqlNode呢?
前面说的MixedSqlNode,就代表了List<SqlNode>集合,所以,它是换汤不换药的经典组合设计模式。
举例:ForEachSqlNode内部,可能是一个StaticTextSqlNode,看XML就一目了然。
(Made In EDrawMax)
上面的例子,可以清楚看出,<choose>标签是和<when>、<otherwise>标签配合使用的,创建ChooseSqlNode时,就同时创建了when、otherwise的逻辑,而when会转换为if标签处理,otherwise则转换为SqlNode处理,一般是StaticTextSqlNode。
因篇幅问题,我们不再逐一描述,读者可自行查看。
(Made In IntelliJ Idea IDE)
XMLLanguageDriver:用于创建动态、静态SqlSource。
RawLanguageDriver:在确保只有静态sql时,可以使用,不得含有任何动态sql的内容,否则,请使用XMLLanguageDriver。它其实是对XMLLanguageDriver创建的结果进行唯静态sql检查而已,发现有动态sql的内容,就抛异常。
总结:本篇博文,介绍了Mybatis动态sql的基本结构,基本用法,基本设计原理,下一篇将仔细分析Mybatis使用OGNL计算表达式以及处理#{name}和${name}的时机和它们之间的异同。面试过程中,面试官可能会问你在Mybatis中,#{name}和${name}有什么区别。
版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)
上一篇博文中,介绍了可复用的sql片段,通过<include>标签进行引入,而<include>标签内一般存放的是静态sql,其实,sql片段也是可以放置动态sql标签内容。
1. Mybatis支持的动态sql及基本用法
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.nodeHandlers(String)部分源码。NodeHandler nodeHandlers(String nodeName) { Map<String, NodeHandler> map = new HashMap<String, NodeHandler>(); map.put("trim", new TrimHandler()); map.put("where", new WhereHandler()); map.put("set", new SetHandler()); map.put("foreach", new ForEachHandler()); map.put("if", new IfHandler()); map.put("choose", new ChooseHandler()); map.put("when", new IfHandler()); map.put("otherwise", new OtherwiseHandler()); map.put("bind", new BindHandler()); return map.get(nodeName); }
Mybatis所支持的动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。
<select id="findStudents" parameterType="customMap" resultType="StudentResult"> select * from STUDENTS where 1 = 1 <choose> <when test="name != null"> and name = #{name} </when> <when test="email != null"> and EMAIL = #{email} </when> <otherwise> and PHONE = "123" </otherwise> </choose> </select> <select id="countAll" resultType="int"> select count(1) from ( select stud_id as studId , name, email , dob , phone from students <where> <if test="id != null"> AND STUD_ID < 310 </if> </where> ) tmp </select> <select id="findAllStudents" resultMap="StudentResult" parameterMap="customMap"> <bind name="status" value="'status'"/> SELECT * FROM STUDENTS WHERE STUD_ID > #{id}, #{status},${driver} </select> <insert id="insertStudents" useGeneratedKeys="true" keyProperty="studId" parameterType="java.util.ArrayList"> INSERT INTO STUDENTS(STUD_ID, NAME, EMAIL, DOB, PHONE) VALUES <foreach collection="list" item="item" index="index" separator=","> (#{item.studId},#{item.name},#{item.email},#{item.dob}, #{item.phone}) </foreach> </insert>
为了避免篇幅过长,我们简单列举了几个动态sql的基本用法,我们的重点依然是剖析Mybatis动态sql的底层设计原理。
2. SqlSource
在Mybatis中,每一个select|insert|update|delete标签,都会被解析为一个MappedStatement对象,SqlSource就是MappedStatement对象中一个属性,其最终执行的sql字符串就是由SqlSource提供的。public final class MappedStatement { private SqlSource sqlSource; }
org.apache.ib
3ff0
atis.builder.xml.XMLStatementBuilder.parseStatementNode()部分源码:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
(Made In IntelliJ Idea IDE)
DynamicSqlSource:处理动态sql。
RawSqlSource:处理静态sql,其内部装饰StaticSqlSource。
StaticSqlSource:处理静态sql,无论是静态sql,还是动态sql,最终的处理结果,都是静态sql。
ProviderSqlSource:处理注解Annotation形式的sql。
DynamicSqlSource和StaticSqlSource的最大区别在于:StaticSqlSource的String sql,可以直接获取使用,而DynamicSqlSource的String sql需要逐一根据条件解析并拼接出最终的sql,方能使用。
3. DynamicSqlSource以及SqlNode
public class DynamicSqlSource implements SqlSource { private Configuration configuration; private SqlNode rootSqlNode; }
public interface SqlNode { boolean apply(DynamicContext context); }
boolean apply(DynamicContext context):该方法的含义为,将sql的处理结果,append到DynamicContext context对象中,DynamicContext可以理解为StringBuilder对象的功能,它的作用就是计算sql片段并append到一起,形成最终的sql。对该方法的理解非常重要,只有理解了这个方法,才能真正明白一个完整sql是如何组装出来的。
下面的伪代码,展示了SqlNode.apply(DynamicContext)方法设计的核心原理。
StringBuilder sb = new StringBuilder(); IfSqlNode.apply(StringBuilder sb) { sb.append("select "); } SetSqlNode.apply(StringBuilder sb) { sb.append("* from ss "); } sb.toString(); //output: select * from ss
DynamicSqlSource为动态sql源,而SqlNode则具体代表了动态sql源中具体的动态sql类型。
(Made In IntelliJ Idea IDE)
上面的SqlNode,基本上见名知意,我们着重解释一下容易迷惑的两个SqlNode。
VarDeclSqlNode:处理动态sql标签<bind>的SqlNode类。
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) { final Object value = OgnlCache.getValue(expression, context.getBindings()); // 由于没有sql可append,仅是把bind标签的变量名和值保存至上下文参数列表内 context.bind(name, value); return true; } }
MixedSqlNode:意为混合的SqlNode,它保存了其他多种SqlNode的集合,可以看做是一个List<SqlNode>列表,事实也确实如此。
DynamicSqlSource中的SqlNode rootSqlNode属性,通常都是MixedSqlNode对象(完全是静态sql时,可能是一个StaticTextSqlNode),而MixedSqlNode对象又保存了所有的List<SqlNode>集合,这也是通过一个rootSqlNode,就能找到所有SqlNode的深层原因。
4. SqlNode的组合设计模式
public class ForEachSqlNode implements SqlNode { private SqlNode contents; }
SqlNode,采用了组合设计模式,组合设计模式可以用来表示经典的树型结构,有人不禁要问,组合设计模式,它的属性,应该List<SqlNode>集合,怎么会是单一的SqlNode呢?
前面说的MixedSqlNode,就代表了List<SqlNode>集合,所以,它是换汤不换药的经典组合设计模式。
举例:ForEachSqlNode内部,可能是一个StaticTextSqlNode,看XML就一目了然。
<foreach collection="list" item="item" index="index" separator=","> (#{item.studId},#{item.name},#{item.email},#{item.dob}, #{item.phone}) </foreach>
5. NodeHandler
SqlNode是由NodeHandler创建出来的。(Made In EDrawMax)
private class ChooseHandler implements NodeHandler { public ChooseHandler() { // Prevent Synthetic Access } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>(); List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>(); handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes); SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes); ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode); 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 = nodeHandlers(nodeName); if (handler instanceof IfHandler) { handler.handleNode(child, ifSqlNodes); } else if (handler instanceof OtherwiseHandler) { handler.handleNode(child, defaultSqlNodes); } } } //... }
上面的例子,可以清楚看出,<choose>标签是和<when>、<otherwise>标签配合使用的,创建ChooseSqlNode时,就同时创建了when、otherwise的逻辑,而when会转换为if标签处理,otherwise则转换为SqlNode处理,一般是StaticTextSqlNode。
map.put("if", new IfHandler()); map.put("when", new IfHandler());
因篇幅问题,我们不再逐一描述,读者可自行查看。
6. LanguageDriver
LanguageDriver是一个辅助工具类,用于创建SqlSource。(Made In IntelliJ Idea IDE)
XMLLanguageDriver:用于创建动态、静态SqlSource。
RawLanguageDriver:在确保只有静态sql时,可以使用,不得含有任何动态sql的内容,否则,请使用XMLLanguageDriver。它其实是对XMLLanguageDriver创建的结果进行唯静态sql检查而已,发现有动态sql的内容,就抛异常。
/** * As of 3.2.4 the default XML language is able to identify static statements * and create a {@link RawSqlSource}. So there is no need to use RAW unless you * want to make sure that there is not any dynamic tag for any reason. * * @since 3.2.0 * @author Eduardo Macarron */ public class RawLanguageDriver extends XMLLanguageDriver { @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { SqlSource source = super.createSqlSource(configuration, script, parameterType); checkIsNotDynamic(source); return source; } // ... }
总结:本篇博文,介绍了Mybatis动态sql的基本结构,基本用法,基本设计原理,下一篇将仔细分析Mybatis使用OGNL计算表达式以及处理#{name}和${name}的时机和它们之间的异同。面试过程中,面试官可能会问你在Mybatis中,#{name}和${name}有什么区别。
版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)
相关文章推荐
- Mybatis3.4.x技术内幕(十八):Mybatis之动态Sql设计原本(下)
- Mybatis3.3.x技术内幕(四):五鼠闹东京之执行器Executor设计原本
- Mybatis3.4.x技术内幕(十六):Mybatis之sqlFragment(可复用的sql片段)
- Mybatis3.4.x技术内幕(十九):Mybatis之plugin插件设计原理
- Mybatis3.3.x技术内幕(一):SqlSession和SqlSessionFactory列传
- Mybatis3.4.x技术内幕(二十三):Mybatis面试问题集锦(大结局)
- Mybatis3.4.x技术内幕(二十一):参数设置、结果封装、级联查询、延迟加载原理分析
- Mybatis3.3.x技术内幕(十一):执行一个Sql命令的完整流程
- 原 荐 Mybatis3.4.x技术内幕(二十三):Mybatis面试问题集锦(大结局)
- mybatis系列技术之三 动态SQL
- Mybatis3.3.x技术内幕(二):动态代理之投鞭断流(自动映射器Mapper的底层实现原理)
- 《Microsoft SQL Server 2005技术内幕:T-SQL查询》 勘误表
- 《SQL Server 2005技术内幕:T-SQl查询》:T-SQL进阶必读
- Microsoft SQL Server 2005技术内幕系列书籍
- 《SQL Server 2005技术内幕:T-SQl查询》:常见sql问题归类解答
- 《SQL Server 2005技术内幕:T-SQl查询》:T-SQL进阶必读
- (笔记)《SQL 2005 技术内幕 T-SQL查询》第一章 逻辑查询处理
- MFC执行期类型识别与动态创建技术内幕
- SQL查询的逻辑执行顺序,来自技术内幕
- MSSQL逻辑查询的步骤 摘自:Microsoft SQL Server 2005技术内幕:T-SQL查询