当前位置:  首页  >  PHP资讯  >  业界资讯

自己动手实现mybatis动态sql的方法

下面小编就为大家分享一篇自己动手实现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的写法,只是想写一个尽量复杂的嵌套结构,如果把这种复杂的情况实现了,那么简单一点的就更加不在话下了。

 delete from pl_pagewidget  where pagewidgetcode in   #{item}   #{b}     and a = #{a} 

要实现解析出上面例子的sql,首先一个难点类似是test属性里的条件怎么判断真假,不过这个难点在struts2中学到的ognl表达式面前就比较小儿科了。不知道有么有朋友遇到过一个比较奇葩的现象,就是有时候明明在mybatis动态sql中写如下表达式,但是当n=0的时候居然是满足条件的也就是test里的值是false,0居然不能满足这个表达式的条件,这里就是ognl库的原因了。没办法它就是这么玩的,当成特殊情况记住就可以了

 test="n != null and n !=''"

ognl表达式使用很方便如下

 import java.util.HashMap; import java.util.Map; import ognl.Ognl; public class OgnlTest { //输出结果:false public static void main(String[] args) throws Exception { String con1 = "n != null and n != ''"; Map root = new HashMap<>(); root.put("n", 0); System.out.println(Ognl.getValue(con1,root)); } }

要实现解析上面例子的sql,第二个难点就是虽然这个sql披上一层xml的皮就是一个标准的sql,如下

  delete from pl_pagewidget  where pagewidgetcode in   #{item}   #{b}     and a = #{a}  

但是要解析上面的xml和我们平时不一样,这个xml是标签和文本混合的,正常我们开发中应该很少会用到解析这种xml。不过我们常用的解析xml的工具dom4j其实可以很好的解析这种sql,只不过很少可能用到。Element类的content()方法就可以返回一个Node的集合,再通过遍历这个集合,判断每个Node的类型就可以了。解决了这两个重点,只需要加上一点技巧就可以解析这种动态sql了。

我用到的技巧是根据java语法格式得到的启发。比如java中有局部变量和全局变量,不考虑引用传递这种情况,如果全局变量int i = 1;方法里面传入这个全局变量,然后在方法里面修改,在方法里面看到的是改变后的值,但是在方法外面看到的仍然是1。这个现象其实学过java应该都知道。还有就是当方法调用的时候,方法里面可以看到全局变量,也可以看到局部变量,方法调用结束后局部变量会被清空释放(看垃圾搜集器高兴)。介绍了这些直接上代码了

 import java.io.StringReader; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.Text; import org.dom4j.io.SAXReader; import com.rd.sql.Attrs; import com.rd.sql.BaseNode; import com.rd.sql.NodeFactory; public class SqlParser { private Map currParams = new HashMap(); /** delete from pl_pagewidget  where pagewidgetcode in   #{item}   #{b}     and a = #{a}  */ public static void main(String[] args) throws Exception { Map map = new HashMap(); map.put("widgetcodes", Arrays.asList("1", "2")); map.put("bs", Arrays.asList("3", "4")); map.put("a", 1); SqlParser parser = new SqlParser(); System.out .println(parser.parser("delete from pl_pagewidget\n" + "\t\n" + "\t\twhere pagewidgetcode in\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\tand a = #{a}\n" + "\t\n", map)); System.out.println(parser.getParams()); } public String parser(String xml, Map params) throws Exception { // xml = "<&#63;xml version=\"1.0\" encoding=\"UTF-8\"&#63;>"+xml; //给输入的动态sql套一层xml标签 xml = ""+xml+""; SAXReader reader = new SAXReader(false); Document document = reader.read(new StringReader(xml)); Element element = document.getRootElement(); Map currParams = new HashMap(); StringBuilder sb = new StringBuilder(); //开始解析 parserElement(element, currParams, params, sb); return sb.toString(); } /** * 使用递归解析动态sql * @param ele1 待解析的xml标签 * @param currParams * @param globalParams * @param sb * @throws Exception */ private void parserElement(Element ele1, Map currParams, Map globalParams, StringBuilder sb) throws Exception { // 解析一个节点,比如解析到了一个if节点,假如test判断为true这里就返回true TempVal val = parserOneElement(currParams, globalParams, ele1, sb); //得到解析的这个节点的抽象节点对象 BaseNode node = val.getNode(); /** * 实际上这句之上的语句只是解析了xml的标签,并没有解析标签里的内容,这里 * 表示要解析内容之前,如果有前置操作做一点前置操作 */ node.pre(currParams, globalParams, ele1, sb); //判断是否还需要解析节点里的内容,例如if节点test结果为true boolean flag = val.isContinue(); // 得到该节点下的所有子节点的集合,包含普通文本 List nodes = ele1.content(); if (flag && !nodes.isEmpty()) { /** * 这里表示要进一步解析节点里的内容了,可以把节点类比成一个方法的外壳 * 里面的内容类比成方法里的具体语句,开始解析节点的内容之前 * 先创建本节点下的局部参数的容器,最方便当然是map */ Map params = new HashMap(); /** * 把外面传进来的局部参数,直接放入容器,由于本例中参数都是常用数据类型 * 不会存在引用类型所以,这里相当于是一个copy,为了不影响外面传入的对象 * 可以类比方法调用传入参数的情况 */ params.putAll(currParams); //循环所有子节点 for (int i = 0; i  params,Map globalParams) throws Exception { //获取foreach这种标签中用于记录循环的变量 String indexStr = MapUtils.getString(params, Attrs.WHILE_INDEX); Integer index = null; if(StringUtils.isNotEmpty(indexStr)) { index = MapUtils.getInteger(params, indexStr); } //匹配#{a}这种参数 String reg1 = "(#\\{)(\\w+)(\\})"; //匹配${a}这种参数 String reg2 = "(\\$\\{)(\\w+)(\\})"; Pattern p1 = Pattern.compile(reg1); Matcher m1 = p1.matcher(str); Pattern p2 = Pattern.compile(reg2); Matcher m2 = p2.matcher(str); String whileList = MapUtils.getString(params, Attrs.WHILE_LIST); Map allParams = getAllParams(params, globalParams); while(m1.find()) { String tmpKey = m1.group(2); String key = whileList == null&#63;tmpKey:(whileList+"_"+tmpKey); key = index == null&#63;key:(key+index); String reKey = "#{"+key+"}"; //如果在foreach类似的循环里,可能需要将参数#{xx}替换成#{xx_0},#{xx_1} str = str.replace(m1.group(0), reKey); currParams.put(key, allParams.get(tmpKey)); } while(m2.find()) { String tmpKey = m2.group(2); Object value = allParams.get(tmpKey); if(value != null) { str = str.replace(m2.group(0), getValue(value)); } } return str; } private String getValue(Object value) { String result = ""; if(value instanceof Date) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); result = sdf.format((Date)value); } else { result = String.valueOf(value); } return result; } private Map getAllParams(Map currParams, Map globalParams) { Map allParams = new HashMap(); allParams.putAll(globalParams); allParams.putAll(currParams); return allParams; } // 解析一个xml元素 private TempVal parserOneElement(Map currParams, Map globalParams, Element ele, StringBuilder sb) throws Exception { //获取xml标签名 String eleName = ele.getName(); //解析一个节点后是否继续,如遇到if这种节点,就需要判断test里是否为空 boolean isCOntinue= false; //声明一个抽象节点 BaseNode node = null; if (StringUtils.isNotEmpty(eleName)) { //使用节点工厂根据节点名得到一个节点对象比如是if节点还是foreach节点 node = NodeFactory.create(eleName); //解析一下这个节点,返回是否还需要解析节点里的内容 isCOntinue= node.parse(currParams, globalParams, ele, sb); } return new TempVal(isContinue, ele, node); } public Map getParams() { return currParams; } /** * 封装一个xml元素被解析后的结果 * @author rongdi */ final static class TempVal { private boolean isContinue; private Element ele; private BaseNode node; public TempVal(boolean isContinue, Element ele, BaseNode node) { this.isCOntinue= isContinue; this.ele = ele; this.node = node; } public boolean isContinue() { return isContinue; } public void setContinue(boolean isContinue) { this.isCOntinue= isContinue; } public Element getEle() { return ele; } public void setEle(Element ele) { this.ele = ele; } public BaseNode getNode() { return node; } public void setNode(BaseNode node) { this.node = node; } } }
 import org.dom4j.Element; import java.util.HashMap; import java.util.Map; /** * 抽象节点 * @author rongdi */ public abstract class BaseNode { public abstract boolean parse(Map currParams, Map globalParams, Element ele,StringBuilder sb) throws Exception; public void pre(Map currParams,Map globalParams,Element ele,StringBuilder sb) throws Exception { } public void after(Map currParams,Map globalParams,Element ele,StringBuilder sb) throws Exception { } protected Map getAllParams(Map currParams, Map globalParams) { Map allParams = new HashMap(); allParams.putAll(globalParams); allParams.putAll(currParams); return allParams; } }
 import java.util.Map; import ognl.Ognl; import org.apache.commons.lang.StringUtils; import org.dom4j.Element; /** * if节点 * @author rongdi */ public class IfNode extends BaseNode{ @Override public boolean parse(Map currParams, Map globalParams, Element ele,StringBuilder sb) throws Exception { //得到if节点的test属性 String testStr = ele.attributeValue("test"); boolean test = false; try { if(StringUtils.isNotEmpty(testStr)) { //合并全局变量和局部变量 Map allParams = getAllParams(currParams,globalParams); //使用ognl判断true或者false test = (Boolean) Ognl.getValue(testStr,allParams); } } catch (Exception e) { e.printStackTrace(); throw new Exception("判断操作参数"+testStr+"不合法"); } if(ele.content() != null && ele.content().size()==0) { test = true; } return test; } }
 import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import ognl.Ognl; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.dom4j.Element; /** foreach节点属性如下 collection 需要遍历的集合 item 遍历集合后每个元素存放的变量 index 遍历集合的索引数如0,1,2... separator 遍历后以指定分隔符拼接 open 遍历后拼接开始的符号如 ( close 遍历后拼接结束的符号如 ) */ public class ForeachNode extends BaseNode { @Override public boolean parse(Map currParams, Map globalParams, Element ele, StringBuilder sb) throws Exception { String cOnditionStr= null; String collectiOnStr= ele.attributeValue("collection"); String itemStr = ele.attributeValue("item"); String index = ele.attributeValue("index"); String separatorStr = ele.attributeValue("separator"); String openStr = ele.attributeValue("open"); String closeStr = 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+"_")); } boolean cOndition= true; Map allParams = getAllParams(currParams,globalParams); Object collection = null; if(StringUtils.isNotEmpty(collectionStr)) { //得到待循环的集合 collection = Ognl.getValue(collectionStr,allParams); //如果集合属性不为空,但是条件为null则默认加上一个边界条件 if(StringUtils.isEmpty(conditionStr)) { //这里只是用集合演示一下,也可以再加上数组,只不过改成.length而已 if(collection instanceof List) { cOnditionStr= index+"_<"+collectionStr+".size()"; } else if(collection instanceof Map){ Map map = (Map)collection; Set set = map.entrySet(); List list = new ArrayList(set); allParams.put("_list_", list); cOnditionStr= index+"_<_list_"+".size()"; } } } currParams.remove(Attrs.WHILE_END); if(StringUtils.isNotEmpty(conditionStr)) { //计算条件的值 cOndition= (Boolean)Ognl.getValue(conditionStr,allParams); Map tempMap = new HashMap<>(); tempMap.putAll(allParams); tempMap.put(index+"_",(Integer)currParams.get(index+"_") + 1); currParams.put(Attrs.WHILE_END,!(Boolean)Ognl.getValue(conditionStr,tempMap)); } boolean flag = true; currParams.put(Attrs.WHILE_INDEX, index); currParams.put(Attrs.WHILE_FLAG, true); if(condition) { try { if(StringUtils.isNotEmpty(itemStr) && StringUtils.isNotEmpty(collectionStr)) { Object value = null; int idx = Integer.parseInt(currParams.get(index+"_").toString()); if(collection instanceof List) { value = ((List)collection).get(idx); currParams.put(itemStr, value); } else if(collection instanceof Map){ Map map = (Map)collection; Set> set = map.entrySet(); List> list = new ArrayList(set); currParams.put(itemStr, list.get(idx).getValue()); currParams.put(index, list.get(idx).getKey()); } } } catch (Exception e) { throw new Exception("从集合或者映射取值"+currParams.get(index)+"错误"+e.getMessage()); } } else { flag = false; destroyVars(currParams, index, itemStr); } return flag; } /** * 如果是第一次进入循环标签,则拼上open的内容 */ @Override public void pre(Map currParams, Map globalParams, Element ele, StringBuilder sb) throws Exception { super.pre(currParams, globalParams, ele, sb); boolean start = MapUtils.getBoolean(currParams,Attrs.WHILE_START,false); if(start) { String open = MapUtils.getString(currParams,Attrs.WHILE_OPEN); sb.append(open); } } /** * 如果是最后进入循环标签,则最后拼上close的内容 */ @Override public void after(Map currParams, Map globalParams, Element ele, StringBuilder sb) throws Exception { super.after(currParams, globalParams, ele, sb); boolean end = MapUtils.getBoolean(currParams,Attrs.WHILE_END,false); String separator = MapUtils.getString(currParams,Attrs.WHILE_SEPARATOR); if(!end && StringUtils.isNotEmpty(separator)) { sb.append(separator); } if(end) { String close = MapUtils.getString(currParams,Attrs.WHILE_CLOSE); if(sb.toString().endsWith(separator)) { sb.deleteCharAt(sb.length() - 1); } sb.append(close); } } //释放临时变量 private void destroyVars(Map currParams, String index,String varStr) { 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);     } } import org.dom4j.Element; import java.util.Map; public class SqlNode extends BaseNode{ @Override public boolean parse(Map currParams, Map globalParams, Element ele,StringBuilder sb) throws Exception { return true; } } import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 节点工厂 */ public class NodeFactory { private static Map nodeMap = new ConcurrentHashMap(); private final static List whileList = Arrays.asList("foreach"); static { nodeMap.put("if", new IfNode()); nodeMap.put("sql", new SqlNode()); nodeMap.put("foreach", new ForeachNode()); } public static boolean isWhile(String elementName) { return whileList.contains(elementName); } public static void addNode(String nodeName,BaseNode node) { nodeMap.put(nodeName, node); } public static BaseNode create(String nodeName) { return nodeMap.get(nodeName); } } /** * 各种标记 * @author rongdi */ public class Attrs { public final static String TRANSACTIOnAL= "transactional"; public final static String WHILE_START = "while-start"; public final static String WHILE_END = "while-end"; public final static String WHILE_OPEN = "while-open"; public final static String WHILE_CLOSE = "while-close"; public final static String WHILE_SEPARATOR = "while-separator"; public final static String WHILE_INDEX = "while-index"; public final static String WHILE_FLAG = "while-flag"; public final static String WHILE_LIST = "while-list"; public final static String WHEN_FLAG = "when-flag"; public static final String PROCESS_VAR = "process-var"; public final static String RESULT_FLAG = "result-flag"; public final static String RETURN_FLAG = "return-flag"; public final static String CONSOLE_VAR= "console-var"; public final static String DO = "do"; public final static String INDEX = "index"; public final static String COnDITION= "condition"; public final static String NAME= "name"; public final static String VALUE= "value"; public static final String TYPE = "type"; public static final String FORMAT = "format"; public static final String IF = "if"; public static final String ELSE = "else"; public final static String FILE= "file"; public static final String DATE = "date"; public static final String NOW = "now"; public static final String DECIMAL = "decimal"; public static final String ID = "id"; public static final String PARAMS = "params"; public static final String TARGET = "target"; public static final String SINGLE = "single"; public static final String PAGING = "paging"; public static final String DESC = "desc"; public static final String BREAK = "break"; public static final String COnTINUE= "continue"; public static final String COLLECTION = "collection"; public static final String VAR = "var"; public static final String EXECUTOR = "executor-1"; public static final String ROLLBACK_FLAG = "rollback-flag"; public static final String SERVICE = "service"; public static final String REF = "ref"; public static final String BIZS = "bizs"; public static final String TITLES = "titles"; public static final String COLUMNS = "columns"; public static final String CURRUSER = "currUser"; public static final String CURRPERM = "currPerm"; public static final String TASK_EXECUTOR = "taskExecutor"; public static final String DELIMITER = "delimiter"; public static final String OPERNAME = "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的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。

内容推荐:免费高清PNG素材下载
吐了个 "CAO" !
扫码关注 PHP1 官方微信号
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved PHP1.CN 第一PHP社区 版权所有 京ICP备19059560号-4