自己动手实现mybatis动态sql的方法
发现要坚持写博客真的是一件很困难的事情,各种原因都会导致顾不上博客。本来打算写自己动手实现orm,看看时间,还是先实现一个动态sql,下次有时间再补上orm完整的实现吧。
用过mybatis的人,估计对动态sql都不陌生,如果没有用过,就当看看热闹吧。我第一次接触mysql是在大四的时候,当时就觉得动态sql这东西很牛,很灵活,一直想搞明白怎么实现的,尽管当时已经能够写ioc,mvc和简单的orm框架(仿mybaits但是没有动态sql部分),但是仍然找不到mybatis核心的动态sql到底在哪实现的,怎么实现的,可能是那些代码太绕根本没法看懂,直到目前,我都没有勇气去看mybatis的动态sql部分,大概是天生对算法有莫名其妙的敬畏吧。
几年前因为想做一个配置平台,想用解析型语言替代java的实现,可以让配置人员在页面上方便的编写少量代码实现复杂的业务逻辑(包括数据库操作)。当时java已经有js解析引擎,但是大多数人都说效率太低,不知道我发什么疯就想到自己实现一个解析语言。不过实现自己的语言也是我一直的梦想,解析语言相对编译型语言入手简单,于是我就果断动手了,写完才知道,其实自己的实现估计还没有当时的js引擎效率高,那时的我真的是很年轻很简单。今天谈到的动态sql实现其实就是受到那时候解析语言的启发。
废话不多说直接开始聊动态sql,请看下面例子,先声明这里的例子并不是一个正确的sql的写法,只是想写一个尽量复杂的嵌套结构,如果把这种复杂的情况实现了,那么简单一点的就更加不在话下了。
deletefrompl_pagewidgetwherepagewidgetcodein #{item} #{b} anda=#{a} 
要实现解析出上面例子的sql,首先一个难点类似是test属性里的条件怎么判断真假,不过这个难点在struts2中学到的ognl表达式面前就比较小儿科了。不知道有么有朋友遇到过一个比较奇葩的现象,就是有时候明明在mybatis动态sql中写如下表达式,但是当n=0的时候居然是满足条件的也就是test里的值是false,0居然不能满足这个表达式的条件,这里就是ognl库的原因了。没办法它就是这么玩的,当成特殊情况记住就可以了
test="n!=nullandn!=''"
ognl表达式使用很方便如下
importjava.util.HashMap;
importjava.util.Map;
importognl.Ognl;
publicclassOgnlTest{
//输出结果:false
publicstaticvoidmain(String[]args)throwsException{
Stringcon1="n!=nullandn!=''";
Maproot=newHashMap<>();
root.put("n",0);
System.out.println(Ognl.getValue(con1,root));
}
} 
要实现解析上面例子的sql,第二个难点就是虽然这个sql披上一层xml的皮就是一个标准的sql,如下
deletefrompl_pagewidget wherepagewidgetcodein #{item} #{b} anda=#{a} 
但是要解析上面的xml和我们平时不一样,这个xml是标签和文本混合的,正常我们开发中应该很少会用到解析这种xml。不过我们常用的解析xml的工具dom4j其实可以很好的解析这种sql,只不过很少可能用到。Element类的content()方法就可以返回一个Node的集合,再通过遍历这个集合,判断每个Node的类型就可以了。解决了这两个重点,只需要加上一点技巧就可以解析这种动态sql了。
我用到的技巧是根据java语法格式得到的启发。比如java中有局部变量和全局变量,不考虑引用传递这种情况,如果全局变量inti=1;方法里面传入这个全局变量,然后在方法里面修改,在方法里面看到的是改变后的值,但是在方法外面看到的仍然是1。这个现象其实学过java应该都知道。还有就是当方法调用的时候,方法里面可以看到全局变量,也可以看到局部变量,方法调用结束后局部变量会被清空释放(看垃圾搜集器高兴)。介绍了这些直接上代码了
importjava.io.StringReader;
importjava.text.SimpleDateFormat;
importjava.util.Arrays;
importjava.util.Date;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
importorg.apache.commons.collections.MapUtils;
importorg.apache.commons.lang.StringUtils;
importorg.dom4j.Document;
importorg.dom4j.Element;
importorg.dom4j.Node;
importorg.dom4j.Text;
importorg.dom4j.io.SAXReader;
importcom.rd.sql.Attrs;
importcom.rd.sql.BaseNode;
importcom.rd.sql.NodeFactory;
publicclassSqlParser{
privateMapcurrParams=newHashMap();
/**
deletefrompl_pagewidget
wherepagewidgetcodein
#{item}
#{b}
anda=#{a}
*/
publicstaticvoidmain(String[]args)throwsException{
Mapmap=newHashMap();
map.put("widgetcodes",Arrays.asList("1","2"));
map.put("bs",Arrays.asList("3","4"));
map.put("a",1);
SqlParserparser=newSqlParser();
System.out
.println(parser.parser("deletefrompl_pagewidget\n"
+"\t\n"
+"\t\twherepagewidgetcodein\n"
+"\t\t\n"
+"\t\t\n"
+"\t\t#{item}\n"
+"\t\t\n"
+"\t\t\n"
+"\t\t\t#{b}\n"+"\t\t\n"
+"\t\t\n"+"\t\n"
+"\t\n"
+"\t\tanda=#{a}\n"+"\t\n",map));
System.out.println(parser.getParams());
}
publicStringparser(Stringxml,Mapparams)
throwsException{
//xml=""+xml;
//给输入的动态sql套一层xml标签
xml=""+xml+" ";
SAXReaderreader=newSAXReader(false);
Documentdocument=reader.read(newStringReader(xml));
Elementelement=document.getRootElement();
MapcurrParams=newHashMap();
StringBuildersb=newStringBuilder();
//开始解析
parserElement(element,currParams,params,sb);
returnsb.toString();
}
/**
*使用递归解析动态sql
*@paramele1待解析的xml标签
*@paramcurrParams
*@paramglobalParams
*@paramsb
*@throwsException
*/
privatevoidparserElement(Elementele1,MapcurrParams,
MapglobalParams,StringBuildersb)
throwsException{
//解析一个节点,比如解析到了一个if节点,假如test判断为true这里就返回true
TempValval=parserOneElement(currParams,globalParams,ele1,sb);
//得到解析的这个节点的抽象节点对象
BaseNodenode=val.getNode();
/**
*实际上这句之上的语句只是解析了xml的标签,并没有解析标签里的内容,这里
*表示要解析内容之前,如果有前置操作做一点前置操作
*/
node.pre(currParams,globalParams,ele1,sb);
//判断是否还需要解析节点里的内容,例如if节点test结果为true
booleanflag=val.isContinue();
//得到该节点下的所有子节点的集合,包含普通文本
Listnodes=ele1.content();
if(flag&&!nodes.isEmpty()){
/**
*这里表示要进一步解析节点里的内容了,可以把节点类比成一个方法的外壳
*里面的内容类比成方法里的具体语句,开始解析节点的内容之前
*先创建本节点下的局部参数的容器,最方便当然是map
*/
Mapparams=newHashMap();
/**
*把外面传进来的局部参数,直接放入容器,由于本例中参数都是常用数据类型
*不会存在引用类型所以,这里相当于是一个copy,为了不影响外面传入的对象
*可以类比方法调用传入参数的情况
*/
params.putAll(currParams);
//循环所有子节点
for(inti=0;iparams,MapglobalParams)
throwsException{
//获取foreach这种标签中用于记录循环的变量
StringindexStr=MapUtils.getString(params,Attrs.WHILE_INDEX);
Integerindex=null;
if(StringUtils.isNotEmpty(indexStr)){
index=MapUtils.getInteger(params,indexStr);
}
//匹配#{a}这种参数
Stringreg1="(#\\{)(\\w+)(\\})";
//匹配${a}这种参数
Stringreg2="(\\$\\{)(\\w+)(\\})";
Patternp1=Pattern.compile(reg1);
Matcherm1=p1.matcher(str);
Patternp2=Pattern.compile(reg2);
Matcherm2=p2.matcher(str);
StringwhileList=MapUtils.getString(params,Attrs.WHILE_LIST);
MapallParams=getAllParams(params,globalParams);
while(m1.find()){
StringtmpKey=m1.group(2);
Stringkey=whileList==null?tmpKey:(whileList+"_"+tmpKey);
key=index==null?key:(key+index);
StringreKey="#{"+key+"}";
//如果在foreach类似的循环里,可能需要将参数#{xx}替换成#{xx_0},#{xx_1}
str=str.replace(m1.group(0),reKey);
currParams.put(key,allParams.get(tmpKey));
}
while(m2.find()){
StringtmpKey=m2.group(2);
Objectvalue=allParams.get(tmpKey);
if(value!=null){
str=str.replace(m2.group(0),getValue(value));
}
}
returnstr;
}
privateStringgetValue(Objectvalue){
Stringresult="";
if(valueinstanceofDate){
SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
result=sdf.format((Date)value);
}else{
result=String.valueOf(value);
}
returnresult;
}
privateMapgetAllParams(MapcurrParams,
MapglobalParams){
MapallParams=newHashMap();
allParams.putAll(globalParams);
allParams.putAll(currParams);
returnallParams;
}
//解析一个xml元素
privateTempValparserOneElement(MapcurrParams,
MapglobalParams,Elementele,StringBuildersb)
throwsException{
//获取xml标签名
StringeleName=ele.getName();
//解析一个节点后是否继续,如遇到if这种节点,就需要判断test里是否为空
booleanisContinue=false;
//声明一个抽象节点
BaseNodenode=null;
if(StringUtils.isNotEmpty(eleName)){
//使用节点工厂根据节点名得到一个节点对象比如是if节点还是foreach节点
node=NodeFactory.create(eleName);
//解析一下这个节点,返回是否还需要解析节点里的内容
isContinue=node.parse(currParams,globalParams,ele,sb);
}
returnnewTempVal(isContinue,ele,node);
}
publicMapgetParams(){
returncurrParams;
}
/**
*封装一个xml元素被解析后的结果
*@authorrongdi
*/
finalstaticclassTempVal{
privatebooleanisContinue;
privateElementele;
privateBaseNodenode;
publicTempVal(booleanisContinue,Elementele,BaseNodenode){
this.isContinue=isContinue;
this.ele=ele;
this.node=node;
}
publicbooleanisContinue(){
returnisContinue;
}
publicvoidsetContinue(booleanisContinue){
this.isContinue=isContinue;
}
publicElementgetEle(){
returnele;
}
publicvoidsetEle(Elementele){
this.ele=ele;
}
publicBaseNodegetNode(){
returnnode;
}
publicvoidsetNode(BaseNodenode){
this.node=node;
}
}
}                                 
importorg.dom4j.Element;
importjava.util.HashMap;
importjava.util.Map;
/**
*抽象节点
*@authorrongdi
*/
publicabstractclassBaseNode{
publicabstractbooleanparse(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException;
publicvoidpre(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
}
publicvoidafter(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
}
protectedMapgetAllParams(MapcurrParams,
MapglobalParams){
MapallParams=newHashMap();
allParams.putAll(globalParams);
allParams.putAll(currParams);
returnallParams;
}
}           
importjava.util.Map;
importognl.Ognl;
importorg.apache.commons.lang.StringUtils;
importorg.dom4j.Element;
/**
*if节点
*@authorrongdi
*/
publicclassIfNodeextendsBaseNode{
@Override
publicbooleanparse(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
//得到if节点的test属性
StringtestStr=ele.attributeValue("test");
booleantest=false;
try{
if(StringUtils.isNotEmpty(testStr)){
//合并全局变量和局部变量
MapallParams=getAllParams(currParams,globalParams);
//使用ognl判断true或者false
test=(Boolean)Ognl.getValue(testStr,allParams);
}
}catch(Exceptione){
e.printStackTrace();
thrownewException("判断操作参数"+testStr+"不合法");
}
if(ele.content()!=null&&ele.content().size()==0){
test=true;
}
returntest;
}
}   
importjava.util.ArrayList;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
importjava.util.Set;
importognl.Ognl;
importorg.apache.commons.collections.MapUtils;
importorg.apache.commons.lang.StringUtils;
importorg.dom4j.Element;
/**
foreach节点属性如下
collection需要遍历的集合
item遍历集合后每个元素存放的变量
index遍历集合的索引数如0,1,2...
separator遍历后以指定分隔符拼接
open遍历后拼接开始的符号如(
close遍历后拼接结束的符号如)
*/
publicclassForeachNodeextendsBaseNode{
@Override
publicbooleanparse(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
StringconditionStr=null;
StringcollectionStr=ele.attributeValue("collection");
StringitemStr=ele.attributeValue("item");
Stringindex=ele.attributeValue("index");
StringseparatorStr=ele.attributeValue("separator");
StringopenStr=ele.attributeValue("open");
StringcloseStr=ele.attributeValue("close");
if(StringUtils.isEmpty(index)){
index="index";
}
if(StringUtils.isEmpty(separatorStr)){
separatorStr=",";
}
if(StringUtils.isNotEmpty(openStr)){
currParams.put(Attrs.WHILE_OPEN,openStr);
}
if(StringUtils.isNotEmpty(closeStr)){
currParams.put(Attrs.WHILE_CLOSE,closeStr);
}
if(StringUtils.isNotEmpty(collectionStr)){
currParams.put(Attrs.WHILE_LIST,collectionStr);
}
currParams.put(Attrs.WHILE_SEPARATOR,separatorStr);
if(index!=null){
/**
*如果局部变量中存在当前循环变量的值,就表示已经不是第一次进入循环标签了,移除掉开始标记
*并将局部变量值加1
*/
if(currParams.get(index)!=null){
currParams.remove(Attrs.WHILE_START);
currParams.put(index+"_",(Integer)currParams.get(index+"_")+1);
}else{//第一次进入循环标签内
currParams.put(Attrs.WHILE_START,true);
currParams.put(index+"_",0);
}
currParams.put(index,(Integer)currParams.get(index+"_"));
}
booleancondition=true;
MapallParams=getAllParams(currParams,globalParams);
Objectcollection=null;
if(StringUtils.isNotEmpty(collectionStr)){
//得到待循环的集合
collection=Ognl.getValue(collectionStr,allParams);
//如果集合属性不为空,但是条件为null则默认加上一个边界条件
if(StringUtils.isEmpty(conditionStr)){
//这里只是用集合演示一下,也可以再加上数组,只不过改成.length而已
if(collectioninstanceofList){
conditionStr=index+"_<"+collectionStr+".size()";
}elseif(collectioninstanceofMap){
Mapmap=(Map)collection;
Setset=map.entrySet();
Listlist=newArrayList(set);
allParams.put("_list_",list);
conditionStr=index+"_<_list_"+".size()";
}
}
}
currParams.remove(Attrs.WHILE_END);
if(StringUtils.isNotEmpty(conditionStr)){
//计算条件的值
condition=(Boolean)Ognl.getValue(conditionStr,allParams);
MaptempMap=newHashMap<>();
tempMap.putAll(allParams);
tempMap.put(index+"_",(Integer)currParams.get(index+"_")+1);
currParams.put(Attrs.WHILE_END,!(Boolean)Ognl.getValue(conditionStr,tempMap));
}
booleanflag=true;
currParams.put(Attrs.WHILE_INDEX,index);
currParams.put(Attrs.WHILE_FLAG,true);
if(condition){
try{
if(StringUtils.isNotEmpty(itemStr)&&StringUtils.isNotEmpty(collectionStr)){
Objectvalue=null;
intidx=Integer.parseInt(currParams.get(index+"_").toString());
if(collectioninstanceofList){
value=((List)collection).get(idx);
currParams.put(itemStr,value);
}elseif(collectioninstanceofMap){
Mapmap=(Map)collection;
Set>set=map.entrySet();
List>list=newArrayList(set);
currParams.put(itemStr,list.get(idx).getValue());
currParams.put(index,list.get(idx).getKey());
}
}
}catch(Exceptione){
thrownewException("从集合或者映射取值"+currParams.get(index)+"错误"+e.getMessage());
}
}else{
flag=false;
destroyVars(currParams,index,itemStr);
}
returnflag;
}
/**
*如果是第一次进入循环标签,则拼上open的内容
*/
@Override
publicvoidpre(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
super.pre(currParams,globalParams,ele,sb);
booleanstart=MapUtils.getBoolean(currParams,Attrs.WHILE_START,false);
if(start){
Stringopen=MapUtils.getString(currParams,Attrs.WHILE_OPEN);
sb.append(open);
}
}
/**
*如果是最后进入循环标签,则最后拼上close的内容
*/
@Override
publicvoidafter(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
super.after(currParams,globalParams,ele,sb);
booleanend=MapUtils.getBoolean(currParams,Attrs.WHILE_END,false);
Stringseparator=MapUtils.getString(currParams,Attrs.WHILE_SEPARATOR);
if(!end&&StringUtils.isNotEmpty(separator)){
sb.append(separator);
}
if(end){
Stringclose=MapUtils.getString(currParams,Attrs.WHILE_CLOSE);
if(sb.toString().endsWith(separator)){
sb.deleteCharAt(sb.length()-1);
}
sb.append(close);
}
}
//释放临时变量
privatevoiddestroyVars(MapcurrParams,Stringindex,StringvarStr){
currParams.remove(Attrs.WHILE_INDEX);
currParams.remove(Attrs.WHILE_FLAG);
currParams.remove(Attrs.WHILE_SEPARATOR);
currParams.remove(Attrs.WHILE_START);
currParams.remove(Attrs.WHILE_END);
currParams.remove(Attrs.WHILE_LIST);
}
}
importorg.dom4j.Element;
importjava.util.Map;
publicclassSqlNodeextendsBaseNode{
@Override
publicbooleanparse(MapcurrParams,MapglobalParams,Elementele,StringBuildersb)throwsException{
returntrue;
}
}
importjava.util.Arrays;
importjava.util.List;
importjava.util.Map;
importjava.util.concurrent.ConcurrentHashMap;
/**
*节点工厂
*/
publicclassNodeFactory{
privatestaticMapnodeMap=newConcurrentHashMap();
privatefinalstaticListwhileList=Arrays.asList("foreach");
static{
nodeMap.put("if",newIfNode());
nodeMap.put("sql",newSqlNode());
nodeMap.put("foreach",newForeachNode());
}
publicstaticbooleanisWhile(StringelementName){
returnwhileList.contains(elementName);
}
publicstaticvoidaddNode(StringnodeName,BaseNodenode){
nodeMap.put(nodeName,node);
}
publicstaticBaseNodecreate(StringnodeName){
returnnodeMap.get(nodeName);
}
}
/**
*各种标记
*@authorrongdi
*/
publicclassAttrs{
publicfinalstaticStringTRANSACTIONAL="transactional";
publicfinalstaticStringWHILE_START="while-start";
publicfinalstaticStringWHILE_END="while-end";
publicfinalstaticStringWHILE_OPEN="while-open";
publicfinalstaticStringWHILE_CLOSE="while-close";
publicfinalstaticStringWHILE_SEPARATOR="while-separator";
publicfinalstaticStringWHILE_INDEX="while-index";
publicfinalstaticStringWHILE_FLAG="while-flag";
publicfinalstaticStringWHILE_LIST="while-list";
publicfinalstaticStringWHEN_FLAG="when-flag";
publicstaticfinalStringPROCESS_VAR="process-var";
publicfinalstaticStringRESULT_FLAG="result-flag";
publicfinalstaticStringRETURN_FLAG="return-flag";
publicfinalstaticStringCONSOLE_VAR="console-var";
publicfinalstaticStringDO="do";
publicfinalstaticStringINDEX="index";
publicfinalstaticStringCONDITION="condition";
publicfinalstaticStringNAME="name";
publicfinalstaticStringVALUE="value";
publicstaticfinalStringTYPE="type";
publicstaticfinalStringFORMAT="format";
publicstaticfinalStringIF="if";
publicstaticfinalStringELSE="else";
publicfinalstaticStringFILE="file";
publicstaticfinalStringDATE="date";
publicstaticfinalStringNOW="now";
publicstaticfinalStringDECIMAL="decimal";
publicstaticfinalStringID="id";
publicstaticfinalStringPARAMS="params";
publicstaticfinalStringTARGET="target";
publicstaticfinalStringSINGLE="single";
publicstaticfinalStringPAGING="paging";
publicstaticfinalStringDESC="desc";
publicstaticfinalStringBREAK="break";
publicstaticfinalStringCONTINUE="continue";
publicstaticfinalStringCOLLECTION="collection";
publicstaticfinalStringVAR="var";
publicstaticfinalStringEXECUTOR="executor-1";
publicstaticfinalStringROLLBACK_FLAG="rollback-flag";
publicstaticfinalStringSERVICE="service";
publicstaticfinalStringREF="ref";
publicstaticfinalStringBIZS="bizs";
publicstaticfinalStringTITLES="titles";
publicstaticfinalStringCOLUMNS="columns";
publicstaticfinalStringCURRUSER="currUser";
publicstaticfinalStringCURRPERM="currPerm";
publicstaticfinalStringTASK_EXECUTOR="taskExecutor";
publicstaticfinalStringDELIMITER="delimiter";
publicstaticfinalStringOPERNAME="operName";
}
currParams.remove(varStr);
currParams.remove(index);
currParams.remove(index+"_");
}
}                
附上pom文件
4.0.0 com.rd parser jar 1.0-SNAPSHOT myparser http://maven.apache.org dom4j dom4j 1.6.1 opensymphony ognl 2.6.11 commons-collections commons-collections 3.2.1 commons-lang commons-lang 2.6 junit junit 3.8.1 test src/main/java **/*.xml src/main/resources **/* ${project.basedir}/src/test/java ${project.basedir}/src/test/resources org.apache.maven.plugins maven-compiler-plugin 3.1 1.8 1.8 UTF-8 
以上这篇自己动手实现mybatis动态sql的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。