文档章节

Mybatis3.4.x技术内幕(十七):Mybatis之动态Sql设计原本(上)

祖大俊
 祖大俊
发布于 2016/08/20 18:40
字数 1602
阅读 3136
收藏 55

上一篇博文中,介绍了可复用的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 &lt; 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.ibatis.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)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

 

© 著作权归作者所有

祖大俊
粉丝 802
博文 32
码字总数 52477
作品 0
昌平
私信 提问
加载中

评论(2)

祖大俊
祖大俊 博主

引用来自“FinleyLiu”的评论

你的文章写的非常好,收益匪浅,弱弱的问下,类图是IntellJ自动生成的,还是自己画的?
我用intelliJ只能生成当前类及父类或接口的类图,兄弟类的类图无法生成,求解释
类图是IntelliJ自动生成的,当你生成当前类以及父类或接口的类图时,你把其它的类拖拽到当前类图窗口,它就自动生成了。
简单说,就是把你要的类,拖拽到当前类图窗口,拖拽错了,你还可以选中按delete进行删除,很简单。
F
FinleyLiu
你的文章写的非常好,收益匪浅,弱弱的问下,类图是IntellJ自动生成的,还是自己画的?
我用intelliJ只能生成当前类及父类或接口的类图,兄弟类的类图无法生成,求解释
Mybatis3.4.x技术内幕(十八):Mybatis之动态Sql设计原本(下)

上一篇博文中,简要介绍了Mybatis动态sql的基本用法和基本设计结构,本篇博文重点阐述一些动态sql的技术细节,#{name}和${name}的区别,将在本篇博文中揭晓。也许读者早已了解它们之间的区别...

祖大俊
2016/08/21
1K
1
Mybatis3.4.x技术内幕(二十三):Mybatis面试问题集锦(大结局)

Mybatis技术内幕系列博客,从原理和源码角度,介绍了其内部实现细节,无论是写的好与不好,我确实是用心写了,由于并不是介绍如何使用Mybatis的文章,所以,一些参数使用细节略掉了,我们的目...

祖大俊
2016/09/17
15.4K
36
Mybatis3.4.x技术内幕(十九):Mybatis之plugin插件设计原理

大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外。 我们从插件配置、插件编写、插件运行原理、插件注册与执行拦截的时机、初始化插件、分页插件的原理等六个方面...

祖大俊
2016/08/28
4K
3
Mybatis3.4.x技术内幕(十六):Mybatis之sqlFragment(可复用的sql片段)

Mybatis目前最新版本为3.4.0,因此,我也将我的项目由3.3.1替换为3.4.0。在上一篇博文中,详细分析了Mybatis在使用foreach循环进行批量insert,返回主键id列表时,如果使用BatchExecutor,那...

祖大俊
2016/06/05
2.2K
0
Mybatis3.4.x技术内幕(二十):PageHelper分页插件源码及原理剖析

PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件,其实我并不想加上好用两个字,但是为了表扬插件作者开源免费的崇高精神,我毫不犹豫的加上了好用一词作为赞美。 原本以为分页插...

祖大俊
2016/09/10
9.1K
6

没有更多内容

加载失败,请刷新页面

加载更多

js—String的一些方法

<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> var str="Hello boy" /** * 在底......

zhengzhixiang
11分钟前
3
0
vSphere ESXi 主机上的3种VLAN设置

VLAN - Virtual Local Area Network,虚拟局域网,能便捷地组建一个网络分组,并能提供诸多好处。VMware vSphere ESXi主机上,也可以在个层次上通过设置VLAN标签地形式来组建VLAN。从划分的层...

大别阿郎
33分钟前
4
0
elasticsearch 6.x的基本dsl语句

本文使用的谷歌浏览器插件sense,链接如下sense插件(兼容es6.x版本) 查看集群状态 http://106.12.27.130:9200/_cat/health?v 绿色-一切都很好(集群功能齐全) 黄色——所有的数据都是可用...

长恭
47分钟前
12
0
移动端的弹窗滚动禁止body滚动

本文转载于:专业的前端网站➼移动端的弹窗滚动禁止body滚动 前言 最近一个需求是弹窗展示列表,显然是需要一个滚动条的,而滚动到底部就会穿透到body滚动,而阻止默认行为是不行的,这样两个...

前端老手
今天
18
0
设计模式 建造者模式和模板方法模式扩展篇

建造者模式和模板方法模式扩展篇 UML 与抽象工厂模式比较 本模式可以看出与抽象工厂非常类似,都是产生不同的产品,怎么区分这两种设计的使用场景呢 - 建造者模式关注的是基本方法的调...

木本本
今天
24
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部