前段时间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:
<select id="dynamicGetAccountList" parameterClass="Account" resultClass="Account">
select ACC_ID as id,
ACC_FIRST_NAME as firstName,
ACC_LAST_NAME as lastName,
ACC_EMAIL as emailAddress from ACCOUNT
<dynamic prepend="WHERE">
<isNotNull prepend="AND" property="emailAddress">
ACC_EMAIL = #emailAddress#
</isNotNull>
<isNotNull property="idList" prepend=" or ACC_ID in ">
<iterate property="idList" conjunction="," open="(" close=")" >
#id#
</iterate>
</isNotNull>
</dynamic>
</select>
3.0:
<select id="dynamicGetAccountList" parameterType="Account" resultType="Account">
select ACC_ID as id,
ACC_FIRST_NAME as firstName,
ACC_LAST_NAME as lastName,
ACC_EMAIL as emailAddress from ACCOUNT
<where>
<if test="emailAddress != null">ACC_EMAIL = #{emailAddress}</if>
<if test="idList != null">
or ACC_ID IN
<foreach item="id" index="index" open="(" close=")" separator="," collection="idList">
#{idList[${index}]}
</foreach>
</if>
</where>
</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:
private void processBodyChildren(StatementScope statementScope, SqlTagContext ctx, Object parameterObject, Iterator localChildren, PrintWriter out) {
while (localChildren.hasNext()) { //XMLSqlSource 生成的动态SQL列表
SqlChild child = (SqlChild) localChildren.next();
if (child instanceof SqlText) {
... ... //组装SQL语句及映射SQL参数
} else if (child instanceof SqlTag) {
SqlTag tag = (SqlTag) child;
SqlTagHandler handler = tag.getHandler(); //得到动态SQL标签处理器
int response = SqlTagHandler.INCLUDE_BODY;
do {
response = handler.doStartFragment(ctx, tag, parameterObject); //处理开始片段
if (response != SqlTagHandler.SKIP_BODY) { //是否跳过,意思该判断的条件为false
processBodyChildren(statementScope, ctx, parameterObject, tag.getChildren(), pw); //递归处理
StringBuffer body = sw.getBuffer();
response = handler.doEndFragment(ctx, tag, parameterObject, body); //处理结束片段
handler.doPrepend(ctx, tag, parameterObject, body); //组装SQL
}
} while (response == SqlTagHandler.REPEAT_BODY);
... ... }
}
2.3、SqlTagHandle设计
首先看下SqlTagHandle处理类的结果图:

ConditionalTagHandler:
public abstract class ConditionalTagHandler extends BaseTagHandler {
... ...
public abstract boolean isCondition(SqlTagContext ctx, SqlTag tag, Object parameterObject);
public int doStartFragment(SqlTagContext ctx, SqlTag tag, Object parameterObject) {
ctx.pushRemoveFirstPrependMarker(tag);
if (isCondition(ctx, tag, parameterObject)) {
return SqlTagHandler.INCLUDE_BODY;
} else {
return SqlTagHandler.SKIP_BODY;
}
}
... ...
}
IsNullTagHandler:
public class IsNullTagHandler extends ConditionalTagHandler {
private static final Probe PROBE = ProbeFactory.getProbe();
public boolean isCondition(SqlTagContext ctx, SqlTag tag, Object parameterObject) {
if (parameterObject == null) {
return true;
} else {
String prop = getResolvedProperty(ctx, tag);
Object value;
if (prop != null) {
value = PROBE.getObject(parameterObject, prop);
} else {
value = parameterObject;
}
return value == null;
}
}
}
至于其他的相关类,不在这列出了,有兴趣的可以找其源码了解下。
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:
@Test
public void shouldTrimWHEREInsteadOfORForSecondCondition() throws Exception {
/* SELECT * FROM BLOG
<where>
<if test="id != false"> and ID = #{id} </if>
<if test="name != false"> or NAME = #{name} </if>
</where>
*/
final String expected = "SELECT * FROM BLOG WHERE NAME = ?";
DynamicSqlSource source = createDynamicSqlSource(
new TextSqlNode("SELECT * FROM BLOG"),
new WhereSqlNode(mixedContents(
new IfSqlNode(
mixedContents(new TextSqlNode(" and ID = ? ")),"false"), new IfSqlNode(mixedContents(new TextSqlNode(" or NAME = ? ")), "true"))));
BoundSql boundSql = source.getBoundSql(null);
assertEquals(expected, boundSql.getSql());
}
private DynamicSqlSource createDynamicSqlSource(SqlNode... contents)
throws IOException, SQLException {
createBlogDataSource();
final String resource = ".../MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder()
.build(reader);
Configuration configuration = sqlMapper.getConfiguration();
MixedSqlNode sqlNode = mixedContents(contents);
return new DynamicSqlSource(configuration, sqlNode);
}
private MixedSqlNode mixedContents(SqlNode... contents) {
return new MixedSqlNode(Arrays.asList(contents));
}
有经验的人,我想一眼就能看出其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:
public void parseStatementNode(XNode context) {
... ...
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);//再次包装dynamic sql处理链
SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode); //默认初始化DynamicSqlSource
... ...
builderAssistant.addMappedStatement(id, sqlSource, statementType,
sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, keyGenerator,
keyProperty); //将解析的所有属性构建成相应的对象存入全局的申明对象(MappedStatement)中,后面只传递该对象。
}
private List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
String nodeName = child.getNode().getNodeName();
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
|| child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
contents.add(new TextSqlNode(data));
} else {
NodeHandler handler = nodeHandlers.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName "> in SQL statement.");
}
handler.handleNode(child, contents);
}
}
return contents;
}
private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() {
{
put("where", new WhereHandler());
put("set", new SetHandler());
put("foreach", new ForEachHandler());
put("if", new IfHandler());
... ...
}
};
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
private class WhereHandler implements NodeHandler {
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);// 遍历
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);//对应测试用例中的mixedContents方法
WhereSqlNode where = new WhereSqlNode(mixedSqlNode);
targetContents.add(where);
}
}
private class IfHandler implements NodeHandler {
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);//遍历
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);//初始化对应的处理器
targetContents.add(ifSqlNode);//
}
} // 其他的Handle详见ibatis源码~
上面是其解析代码的一部分,我想从这几行代码中,可以看出作者的思想了(遍历XML各节点,以节点名查找相应对应的处理器,分发之该处理器执行"业务分析" — 策略者模式,这样在XML中定义了多少标签,这里就需要多少个类与之对应,但如果策略类太多,这种方式就显得笨拙了)。
以下就是其核心类的一部分源码,先看再说。
3.4、DynamicSqlSource(核心类)
public class DynamicSqlSource implements SqlSource {
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(parameterObject);//组装后的结果存储类
rootSqlNode.apply(context);//调用SqlNode解释sql,并组装成完整的sql(SqlNode的客户端调用就在这)
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
3.5、SqlNode
public interface SqlNode {
public boolean apply(DynamicContext context);
}
MixedSqlNode.class
public class MixedSqlNode implements SqlNode {
... ....
public boolean apply(DynamicContext context) {
//遍历组装的解析内容
for (SqlNode sqlNode : contents) {
// 转发至相关解释器处理
sqlNode.apply(context);
}
return true;
}
}
IfSqlNode.class
public class IfSqlNode implements SqlNode {
... ...
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {//OGNL Expressions
contents.apply(context);
return true; //
}
return false;
}
}
TextSqlNode.class
public class TextSqlNode implements SqlNode {
private String text;
public TextSqlNode(String text) {
this.text = text;
}
public boolean apply(DynamicContext context) {
GenericTokenParser parser = new GenericTokenParser("${", "}", new BindingTokenParser(context));
context.appendSql(parser.parse(text));//组装sql
return true;
}
private static class BindingTokenParser implements GenericTokenParser.TokenHandler {
private DynamicContext context;
public BindingTokenParser(DynamicContext context) {
this.context = context;
}
public String handleToken(String content) {
try {
Object value = Ognl.getValue(content, context.getBindings());
return String.valueOf(value);
} catch (OgnlException e) {
throw new BuilderException("Error evaluating expression '" + content + "'. Cause: " + e, e);
}
}
}
}
通过这些代码,再结合上面的测试用例就能够明白个七七八八,简单说就是解释器模式(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作者在改版时的设计思想的变化,有对照及总结才有提高。我这权当抛砖引玉,如其中有什么错误,请大家指出,并请大家多多指教。

- 大小: 99.1 KB

- 大小: 29.4 KB

- 大小: 41.6 KB
分享到:
相关推荐
标题和描述均聚焦于ibatis 3.0 Dynamic Sql的设计解析以及它与2.x版本的差异,这是一个关于ORM(对象关系映射)框架ibatis在处理动态SQL方面的重要升级和改进的主题。以下是对该主题的深入探讨: ### 重要概念:...
嵌入式八股文面试题库资料知识宝典-华为的面试试题.zip
训练导控系统设计.pdf
嵌入式八股文面试题库资料知识宝典-网络编程.zip
人脸转正GAN模型的高效压缩.pdf
少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip
少儿编程scratch项目源代码文件案例素材-鸡蛋.zip
嵌入式系统_USB设备枚举与HID通信_CH559单片机USB主机键盘鼠标复合设备控制_基于CH559单片机的USB主机模式设备枚举与键盘鼠标数据收发系统支持复合设备识别与HID
嵌入式八股文面试题库资料知识宝典-linux常见面试题.zip
面向智慧工地的压力机在线数据的预警应用开发.pdf
基于Unity3D的鱼类运动行为可视化研究.pdf
少儿编程scratch项目源代码文件案例素材-霍格沃茨魔法学校.zip
少儿编程scratch项目源代码文件案例素材-金币冲刺.zip
内容概要:本文深入探讨了HarmonyOS编译构建子系统的作用及其技术细节。作为鸿蒙操作系统背后的关键技术之一,编译构建子系统通过GN和Ninja工具实现了高效的源代码到机器代码的转换,确保了系统的稳定性和性能优化。该系统不仅支持多系统版本构建、芯片厂商定制,还具备强大的调试与维护能力。其高效编译速度、灵活性和可扩展性使其在华为设备和其他智能终端中发挥了重要作用。文章还比较了HarmonyOS编译构建子系统与安卓和iOS编译系统的异同,并展望了其未来的发展趋势和技术演进方向。; 适合人群:对操作系统底层技术感兴趣的开发者、工程师和技术爱好者。; 使用场景及目标:①了解HarmonyOS编译构建子系统的基本概念和工作原理;②掌握其在不同设备上的应用和优化策略;③对比HarmonyOS与安卓、iOS编译系统的差异;④探索其未来发展方向和技术演进路径。; 其他说明:本文详细介绍了HarmonyOS编译构建子系统的架构设计、核心功能和实际应用案例,强调了其在万物互联时代的重要性和潜力。阅读时建议重点关注编译构建子系统的独特优势及其对鸿蒙生态系统的深远影响。
嵌入式八股文面试题库资料知识宝典-奇虎360 2015校园招聘C++研发工程师笔试题.zip
嵌入式八股文面试题库资料知识宝典-腾讯2014校园招聘C语言笔试题(附答案).zip
双种群变异策略改进RWCE算法优化换热网络.pdf
内容概要:本文详细介绍了基于瞬时无功功率理论的三电平有源电力滤波器(APF)仿真研究。主要内容涵盖并联型APF的工作原理、三相三电平NPC结构、谐波检测方法(ipiq)、双闭环控制策略(电压外环+电流内环PI控制)以及SVPWM矢量调制技术。仿真结果显示,在APF投入前后,电网电流THD从21.9%降至3.77%,显著提高了电能质量。 适用人群:从事电力系统研究、电力电子技术开发的专业人士,尤其是对有源电力滤波器及其仿真感兴趣的工程师和技术人员。 使用场景及目标:适用于需要解决电力系统中谐波污染和无功补偿问题的研究项目。目标是通过仿真验证APF的有效性和可行性,优化电力系统的电能质量。 其他说明:文中提到的仿真模型涉及多个关键模块,如三相交流电压模块、非线性负载、信号采集模块、LC滤波器模块等,这些模块的设计和协同工作对于实现良好的谐波抑制和无功补偿至关重要。
内容概要:本文探讨了在工业自动化和物联网交汇背景下,构建OPC DA转MQTT网关软件的需求及其具体实现方法。文中详细介绍了如何利用Python编程语言及相关库(如OpenOPC用于读取OPC DA数据,paho-mqtt用于MQTT消息传递),完成从OPC DA数据解析、格式转换到最终通过MQTT协议发布数据的关键步骤。此外,还讨论了针对不良网络环境下数据传输优化措施以及后续测试验证过程。 适合人群:从事工业自动化系统集成、物联网项目开发的技术人员,特别是那些希望提升跨协议数据交换能力的专业人士。 使用场景及目标:适用于需要在不同通信协议间建立高效稳定的数据通道的应用场合,比如制造业生产线监控、远程设备管理等。主要目的是克服传统有线网络限制,实现在不稳定无线网络条件下仍能保持良好性能的数据传输。 其他说明:文中提供了具体的代码片段帮助理解整个流程,并强调了实际部署过程中可能遇到的问题及解决方案。
基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档