打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
初学ibatis
 
前段时间ibatis3.0发布出来了,迫不及待,将其源码下载拜读。相对ibatis 2.x来说,3.0已是完全改变。具体我就不在这细说,论坛中有一个帖子介绍了ibatis 3.0的新特征及使用。

      由于其他模块的源码我还未细读,在这篇中,先来讨论Dynamic Sql在ibatis 3.0中的实现并比较2.x对应模块的设计。

 

写在前头的话:

      其实如从设计模式应用角度去看待ibatis 3.0中Dynamic Sql的实现,这篇跟我的上篇(HtmlParser设计解析(1)-解析器模式)相同,都是使用Interpreter模式。

      这篇权当Interpreter模式的另一个demo,认我们体会这些开源项目中设计模式的使用。学习都是从模仿开始的,让 我们吸收高人们的经验,应用于我们实践项目需求中。

 

   从总结中提高:

   一、对比2.x中与3.0的Sqlmap中dynamic sql配置

   2.x:

Xml代码
 
  1. <select id="dynamicGetAccountList" parameterClass="Account" resultClass="Account">    
  2.       select ACC_ID as id,   
  3.       ACC_FIRST_NAME as firstName,   
  4.       ACC_LAST_NAME as lastName,   
  5.       ACC_EMAIL as emailAddress from ACCOUNT   
  6.          
  7.     <dynamic prepend="WHERE">  
  8.       <isNotNull prepend="AND" property="emailAddress">  
  9.         ACC_EMAIL = #emailAddress#   
  10.       </isNotNull>  
  11.       <isNotNull property="idList" prepend=" or ACC_ID in ">      
  12.         <iterate property="idList" conjunction="," open="(" close=")" >      
  13.             #id#     
  14.         </iterate>      
  15.       </isNotNull>      
  16.     </dynamic>  
  17. </select>  

 

   3.0:

Xml代码
 
  1. <select id="dynamicGetAccountList" parameterType="Account" resultType="Account">  
  2.       select ACC_ID as id,   
  3.       ACC_FIRST_NAME as firstName,   
  4.       ACC_LAST_NAME as lastName,   
  5.       ACC_EMAIL as emailAddress from ACCOUNT   
  6.   
  7.     <where>  
  8.         <if test="emailAddress != null">ACC_EMAIL = #{emailAddress}</if>  
  9.         <if test="idList != null">  
  10.             or ACC_ID IN   
  11.             <foreach item="id" index="index" open="(" close=")" separator="," collection="idList">  
  12.                 #{idList[${index}]}   
  13.             </foreach>  
  14.        </if>  
  15.     </where>  
  16. </select>  

      从上面这个简单的比较中,第一感觉3.0了中其dynamic sql更加简洁明了。

      其二,test="emailAddress != null" 添加了OGNL的解释支持,可以动态支持更多的判断,这将不限于原2.x中提供

的判断逻辑,更不需要为每个判断条件加个标签进行配置。

      例如:<if test="id > 10 && id < 20"> ACC_EMAIL = #{emailAddress}</if>

               <if test="Account.emailAddress != null "> ACC_EMAIL = #{emailAddress}</if> ……

 

   二、2.x Dynamic Sql的设计

 

   2.1、2.x中dynamic流程。

   这里帖出,我先前在分析ibatis 2.3时画的一个对dynamic sql的整体使用的时序图,可能会显得乱而复杂。

   2.2、主要类设计

       在这,我们只关注这几个类:XMLSqlSource、DynamicSql、SqlTagHandler (具体类结构图见后)

      XMLSqlSource:相当于一个工厂类,其核心方法parseDynamicTags(),用于解析sql Tag,并判断是否是动态SQL标签。如果true,返回一个DynamicSql对象并创建多个SqlChildt对象添加至动态SQL列表中(addChild());false,返回RawSql对象(简单的SQL语句) 。

     DynamicSql:核心的动态SQL类。其动态条件判断逻辑,参数映射等都发生在这个类中。

     SqlTagHandle:动态条件判断接口,其每个动态SQL标签对应其一个子类。

  接下来,我们具体看下在DynamicSql类中核心方法。

    DynamicSql:

Java代码
 
  1. private void processBodyChildren(StatementScope statementScope, SqlTagContext ctx, Object parameterObject, Iterator localChildren, PrintWriter out) {   
  2.     while (localChildren.hasNext()) { //XMLSqlSource 生成的动态SQL列表   
  3.       SqlChild child = (SqlChild) localChildren.next();   
  4.       if (child instanceof SqlText) {   
  5.           ... ... //组装SQL语句及映射SQL参数   
  6.       } else if (child instanceof SqlTag) {   
  7.         SqlTag tag = (SqlTag) child;   
  8.         SqlTagHandler handler = tag.getHandler(); //得到动态SQL标签处理器   
  9.         int response = SqlTagHandler.INCLUDE_BODY;   
  10.         do {             
  11.           response = handler.doStartFragment(ctx, tag, parameterObject); //处理开始片段   
  12.           if (response != SqlTagHandler.SKIP_BODY) { //是否跳过,意思该判断的条件为false   
  13.             processBodyChildren(statementScope, ctx, parameterObject, tag.getChildren(), pw); //递归处理   
  14.             StringBuffer body = sw.getBuffer();   
  15.             response = handler.doEndFragment(ctx, tag, parameterObject, body); //处理结束片段   
  16.             handler.doPrepend(ctx, tag, parameterObject, body); //组装SQL   
  17.                
  18.           }   
  19.         } while (response == SqlTagHandler.REPEAT_BODY);   
  20.         ... ...    }   
  21. }  

 

    2.3、SqlTagHandle设计

    首先看下SqlTagHandle处理类的结果图:


 

    ConditionalTagHandler:

Java代码
 
  1. public abstract class ConditionalTagHandler extends BaseTagHandler {   
  2.   ... ...   
  3.   public abstract boolean isCondition(SqlTagContext ctx, SqlTag tag, Object parameterObject);   
  4.   
  5.   public int doStartFragment(SqlTagContext ctx, SqlTag tag, Object parameterObject) {   
  6.     ctx.pushRemoveFirstPrependMarker(tag);   
  7.     if (isCondition(ctx, tag, parameterObject)) {   
  8.       return SqlTagHandler.INCLUDE_BODY;   
  9.     } else {   
  10.       return SqlTagHandler.SKIP_BODY;   
  11.     }   
  12.   }   
  13.   ... ...   
  14. }  

 

   IsNullTagHandler:

Java代码
 
  1. public class IsNullTagHandler extends ConditionalTagHandler {   
  2.     private static final Probe PROBE = ProbeFactory.getProbe();   
  3.     public boolean isCondition(SqlTagContext ctx, SqlTag tag, Object parameterObject) {   
  4.         if (parameterObject == null) {   
  5.             return true;   
  6.         } else {   
  7.             String prop = getResolvedProperty(ctx, tag);   
  8.             Object value;   
  9.             if (prop != null) {   
  10.                 value = PROBE.getObject(parameterObject, prop);   
  11.             } else {   
  12.                 value = parameterObject;   
  13.             }   
  14.             return value == null;   
  15.         }   
  16.     }   
  17. }  

   至于其他的相关类,不在这列出了,有兴趣的可以找其源码了解下。

 

   2.4、总结ibatis 2.X Dynamic Sql 的设计

      从上面的分析中,可以体会出作者的dynamic sql这模块的设计思路。从装载sqlmap.xml中各sql配置(时序图中的1步),通过工厂创建DynamicSql和RawSql(时序图中的3步),然后分发之不同的处理器。

      在DynamicSql中则调用SqlTagHandle判断其条件(时序图中的10步)。而SqlTagHandle的设计使用策略者模式,让其不同的子类来处理这个判断逻辑。

      通过一系列的加工,最终组装一个Sql对象,将值set至MappedStatement(时序图中的14步)中,然后MappedStatement对象执行executeQueryWithCallback查询数据(时序图中的17步),这儿会调用先前组装的Sql对象(时序图中的19步)。至于这其中的细节已不在这篇的研究这内。

 

    三、3.0 Dynamic Sql的设计

           至于3.0其基本流程跟2.x是一样的,从装载  -> 参数映射 -> 执行SQL -> 返回结果。我们直接切入主题,分析是核心部分。先从一个简单的Dynamic Sql的测试用例开始。

 

   3.1、 测试用例

    dynamic sql test:

Java代码
 
  1. @Test  
  2. public void shouldTrimWHEREInsteadOfORForSecondCondition() throws Exception {   
  3.  /* SELECT * FROM BLOG  
  4.     <where>  
  5.         <if test="id != false"> and ID = #{id} </if>  
  6.         <if test="name != false"> or NAME = #{name} </if>  
  7.     </where>  
  8.    */  
  9.     final String expected = "SELECT * FROM BLOG WHERE  NAME = ?";   
  10.     DynamicSqlSource source = createDynamicSqlSource(   
  11.             new TextSqlNode("SELECT * FROM BLOG"),    
  12.                 new WhereSqlNode(mixedContents(   
  13.                         new IfSqlNode(   
  14.                                 mixedContents(new TextSqlNode("   and ID = ?  ")),"false"), new IfSqlNode(mixedContents(new TextSqlNode("   or NAME = ?  ")), "true"))));   
  15.     BoundSql boundSql = source.getBoundSql(null);   
  16.     assertEquals(expected, boundSql.getSql());   
  17. }   
  18.   
  19. private DynamicSqlSource createDynamicSqlSource(SqlNode... contents)   
  20.         throws IOException, SQLException {   
  21.     createBlogDataSource();   
  22.     final String resource =  ".../MapperConfig.xml";   
  23.     final Reader reader = Resources.getResourceAsReader(resource);   
  24.     SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder()   
  25.             .build(reader);   
  26.     Configuration configuration = sqlMapper.getConfiguration();   
  27.     MixedSqlNode sqlNode = mixedContents(contents);   
  28.     return new DynamicSqlSource(configuration, sqlNode);   
  29. }   
  30.   
  31. private MixedSqlNode mixedContents(SqlNode... contents) {   
  32.     return new MixedSqlNode(Arrays.asList(contents));   
  33. }  
  

  有经验的人,我想一眼就能看出其3.0中的设计思想,从Test中可以看出,或者我上一篇介绍的HtmlParser NodeFilter。

    YES,在ibatis 3.0 dynamic sql设计正是应用了解释器模式,替换了原在这种需求下相对显得笨拙的策略者模式。

 

   下面具体看下类结构图。

 

   3.2、类结构图

   SqlNode Class Diagram:

   SqlSource Class Diagram:

 

   3.3、配置文件的解析

   在这,我就顺便提下ibatis解析组件对dynamic sql的解析方式,以代码见分晓吧。

 

   XMLStatementBuilder:

Java代码
 
  1. public void parseStatementNode(XNode context) {   
  2.                ...  ...   
  3.     List<SqlNode> contents = parseDynamicTags(context);   
  4.     MixedSqlNode rootSqlNode = new MixedSqlNode(contents);//再次包装dynamic sql处理链   
  5.     SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode); //默认初始化DynamicSqlSource   
  6.                ... ...   
  7.     builderAssistant.addMappedStatement(id, sqlSource, statementType,   
  8.             sqlCommandType, fetchSize, timeout, parameterMap,   
  9.             parameterTypeClass, resultMap, resultTypeClass,   
  10.             resultSetTypeEnum, flushCache, useCache, keyGenerator,   
  11.             keyProperty); //将解析的所有属性构建成相应的对象存入全局的申明对象(MappedStatement)中,后面只传递该对象。   
  12. }   
  13.   
  14. private List<SqlNode> parseDynamicTags(XNode node) {   
  15.     List<SqlNode> contents = new ArrayList<SqlNode>();   
  16.     NodeList children = node.getNode().getChildNodes();   
  17.     for (int i = 0; i < children.getLength(); i++) {   
  18.         XNode child = node.newXNode(children.item(i));   
  19.         String nodeName = child.getNode().getNodeName();   
  20.         if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE   
  21.                 || child.getNode().getNodeType() == Node.TEXT_NODE) {   
  22.             String data = child.getStringBody("");   
  23.             contents.add(new TextSqlNode(data));   
  24.         } else {   
  25.             NodeHandler handler = nodeHandlers.get(nodeName);   
  26.             if (handler == null) {   
  27.                 throw new BuilderException("Unknown element <" + nodeName "> in SQL statement.");   
  28.             }   
  29.             handler.handleNode(child, contents);   
  30.         }   
  31.     }   
  32.     return contents;   
  33. }            
  34. private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {   
  35.     {   
  36.         put("where"new WhereHandler());   
  37.         put("set"new SetHandler());   
  38.         put("foreach"new ForEachHandler());   
  39.         put("if"new IfHandler());   
  40.         ... ...   
  41.     }   
  42. };   
  43. private interface NodeHandler {   
  44.     void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);   
  45. }   
  46. private class WhereHandler implements NodeHandler {   
  47.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {   
  48.         List<SqlNode> contents = parseDynamicTags(nodeToHandle);// 遍历   
  49.         MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);//对应测试用例中的mixedContents方法   
  50.         WhereSqlNode where = new WhereSqlNode(mixedSqlNode);   
  51.         targetContents.add(where);   
  52.     }   
  53. }   
  54. private class IfHandler implements NodeHandler {   
  55.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {   
  56.         List<SqlNode> contents = parseDynamicTags(nodeToHandle);//遍历   
  57.         MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);   
  58.         String test = nodeToHandle.getStringAttribute("test");   
  59.         IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);//初始化对应的处理器   
  60.         targetContents.add(ifSqlNode);//   
  61.     }   
  62. // 其他的Handle详见ibatis源码~  

    上面是其解析代码的一部分,我想从这几行代码中,可以看出作者的思想了(遍历XML各节点,以节点名查找相应对应的处理器,分发之该处理器执行"业务分析" — 策略者模式,这样在XML中定义了多少标签,这里就需要多少个类与之对应,但如果策略类太多,这种方式就显得笨拙了)。

 

    以下就是其核心类的一部分源码,先看再说。

    3.4、DynamicSqlSource(核心类)

Java代码
 
  1. public class DynamicSqlSource implements SqlSource {   
  2.     public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {   
  3.         this.configuration = configuration;   
  4.         this.rootSqlNode = rootSqlNode;   
  5.     }   
  6.     public BoundSql getBoundSql(Object parameterObject) {   
  7.         DynamicContext context = new DynamicContext(parameterObject);//组装后的结果存储类   
  8.         rootSqlNode.apply(context);//调用SqlNode解释sql,并组装成完整的sql(SqlNode的客户端调用就在这)   
  9.         SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);   
  10.         Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();   
  11.         SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);   
  12.         BoundSql boundSql = sqlSource.getBoundSql(parameterObject);   
  13.         for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {   
  14.             boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());   
  15.         }   
  16.         return boundSql;   
  17.     }   
  18. }  

 

   3.5、SqlNode

 

Java代码
 
  1. public interface SqlNode {   
  2.     public boolean apply(DynamicContext context);   
  3. }  

 

    MixedSqlNode.class

Java代码
 
  1. public class MixedSqlNode implements SqlNode {   
  2.         ... ....   
  3.     public boolean apply(DynamicContext context) {   
  4.         //遍历组装的解析内容   
  5.         for (SqlNode sqlNode : contents) {    
  6.             // 转发至相关解释器处理   
  7.             sqlNode.apply(context);    
  8.         }   
  9.         return true;   
  10.     }   
  11. }  

   IfSqlNode.class

Java代码
 
  1. public class IfSqlNode implements SqlNode {   
  2.        ... ...   
  3.     public IfSqlNode(SqlNode contents, String test) {   
  4.         this.test = test;   
  5.         this.contents = contents;   
  6.         this.evaluator = new ExpressionEvaluator();   
  7.     }   
  8.   
  9.     public boolean apply(DynamicContext context) {   
  10.         if (evaluator.evaluateBoolean(test, context.getBindings())) {//OGNL Expressions   
  11.             contents.apply(context);   
  12.             return true//   
  13.         }   
  14.         return false;   
  15.     }   
  16. }  

   TextSqlNode.class

Java代码
 
  1. public class TextSqlNode implements SqlNode {   
  2.     private String text;   
  3.   
  4.     public TextSqlNode(String text) {   
  5.         this.text = text;   
  6.     }   
  7.   
  8.     public boolean apply(DynamicContext context) {   
  9.         GenericTokenParser parser = new GenericTokenParser("${""}"new BindingTokenParser(context));   
  10.         context.appendSql(parser.parse(text));//组装sql   
  11.         return true;   
  12.     }   
  13.   
  14.     private static class BindingTokenParser implements GenericTokenParser.TokenHandler {   
  15.         private DynamicContext context;   
  16.         public BindingTokenParser(DynamicContext context) {   
  17.             this.context = context;   
  18.         }   
  19.         public String handleToken(String content) {   
  20.             try {   
  21.                 Object value = Ognl.getValue(content, context.getBindings());   
  22.                 return String.valueOf(value);   
  23.             } catch (OgnlException e) {   
  24.                 throw new BuilderException("Error evaluating expression '" + content + "'. Cause: " + e, e);   
  25.             }   
  26.         }   
  27.     }   
  28. }  

   通过这些代码,再结合上面的测试用例就能够明白个七七八八,简单说就是解释器模式(Interpreter)的一个demo。

   其中SqlNode接口中方法非常简单,就一个apply(),接受一个DynamicContext的参数。

TextSqlNode 在这扮演终结表达式角色,在这个解释类中再没有contents.apply(context);的方法出现,到此结束,退出遍历,添加一个SQL条件,因在此之前其他解释器已判定所有的指定条件是否符合。

   其他的解释类都是非终结表达式角色,为TextSqlNode做保护判断,是否允许进行这个"地带"。

 

   3.6、总结

   通过上面的分析,我们不难看出,其结构非常简单、清晰。

   需求是:用户通过指定的标签按指定的规则组装业务逻辑。这里必须是指定,因为从上代码中看,其这模块不适用用户自定义扩展。

   解决方案是:XMLStatementBuilder读取配置(每个标签对就一个配置解析类 - 采用策略者模式),生成一个SqlSource的对象。再次,Executor执行时需要得到一个BoundSql对象,这时调用SqlNode对象将符合用户条件的组装成完整SQL(每个标签也同时对应一个解释器或者说条件判断器 - 解释器模式 ),最后从Executor从BoundSql读取需要的值,执行客户端的操作。OVER。其中灵活之处在SqlNode客户端,可由用户自行构造对象链。

 

   我想这时对ibatis 3.0 的dynamic sql设计应有所了解。当然,在这只是粗略的体现其作者的思想,详细、完整还需要看完整源码。

   相对于2.x版本来说,其大致思想及流程是不变的,只是采取不同的方式去处理。相对2.x,3.0 dynamic sql这模块显得更为轻巧,在解释配置时,就将层次分析清楚,然后运用解释器模式有效的配合了,对配置的解释及生成完整的SQL。就像剥竹笋一样,一层一层,清晰可见。

 

   以上是本人粗略里分析的ibatis的dynamic sql这模块并与之2.x的进行简单的比较,简洁的体现ibatis作者在改版时的设计思想的变化,有对照及总结才有提高。我这权当抛砖引玉,如其中有什么错误,请大家指出,并请大家多多指教。
 
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Mybatis中几个重要类
行为型模式:解释器模式
Mybatis/Ibatis,数据库操作的返回值
mybatis实战教程(mybatis in action)之七:实现mybatis分页(源码下载)
设计模式之Interpreter(解释器)
IBatis.net动态SQL语句
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服