你的位置:首页 > 软件开发 > Java > 自己动手实现mybatis动态sql

自己动手实现mybatis动态sql

发布时间:2017-12-09 09:00:14
发现要坚持写博客真的是一件很困难的事情,各种原因都会导致顾不上博客。本来打算写自己动手实现orm,看看时间,还是先实现一个动态sql,下次有时间再补上orm完整的实现吧。  用过mybatis的人,估计对动态sql都不陌生,如果没有用过,就当看看热闹吧。我第一次接触mysql是在 ...

  发现要坚持写博客真的是一件很困难的事情,各种原因都会导致顾不上博客。本来打算写自己动手实现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<if test="widgetcodes != null"> where pagewidgetcode in <foreach collection="widgetcodes" item="item" index="index" open="(" separator="," close=")"> <if test="index == 0">  #{item} </if> <foreach collection="bs" item="b" index="index1" open="(" separator="," close=")">   #{b}  </foreach> </foreach></if><if test="a != null"> and a = #{a}</if>

  要实现解析出上面例子的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<String,Object> root = new HashMap<>();  root.put("n", 0);  System.out.println(Ognl.getValue(con1,root)); }}

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

<sql> delete from pl_pagewidget <if test="widgetcodes != null"> where pagewidgetcode in <foreach collection="widgetcodes" item="item" index="index" open="(" separator="," close=")">  <if test="index == 0">  #{item}  </if>  <foreach collection="bs" item="b" index="index1" open="(" separator="," close=")">   #{b}  </foreach> </foreach> </if> <if test="a != null"> and a = #{a} </if></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<String,Object> currParams = new HashMap<String,Object>();  /**  delete from pl_pagewidget  <if test="widgetcodes != null">   where pagewidgetcode in   <foreach collection="widgetcodes" item="item" index="index" open="(" separator="," close=")">   <if test="index == 0">    #{item}   </if>   <foreach collection="bs" item="b" index="index1" open="(" separator="," close=")">    #{b}   </foreach>   </foreach>  </if>  <if test="a != null">   and a = #{a}  </if>  */ public static void main(String[] args) throws Exception {  Map<String, Object> map = new HashMap<String, Object>();  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<if test=\"widgetcodes != null\">\n"        + "\t\twhere pagewidgetcode in\n"        + "\t\t<foreach collection=\"widgetcodes\" item=\"item\" index=\"index\" open=\"(\" separator=\",\" close=\")\">\n"        + "\t\t <if test=\"index == 0\">\n"        + "\t\t #{item}\n"        + "\t\t </if>\n"        + "\t\t <foreach collection=\"bs\" item=\"b\" index=\"index1\" open=\"(\" separator=\",\" close=\")\">\n"        + "\t\t\t#{b}\n" + "\t\t </foreach>\n"        + "\t\t</foreach>\n" + "\t</if>\n"        + "\t<if test=\"a != null\">\n"        + "\t\tand a = #{a}\n" + "\t</if>\n", map));  System.out.println(parser.getParams()); } public String parser(String  params)   throws Exception {  // //给输入的动态sql套一层  ;  SAXReader reader = new SAXReader(false);  Document document = reader.read(new StringReader(= document.getRootElement();  Map<String, Object> currParams = new HashMap<String, Object>();  StringBuilder sb = new StringBuilder();  //开始解析  parserElement(element, currParams, params, sb);  return sb.toString(); } /**  * 使用递归解析动态sql  * @param ele1 待解析的@param currParams  * @param globalParams  * @param sb  * @throws Exception  */ private void parserElement(Element ele1, Map<String, Object> currParams,   Map<String, Object> globalParams, StringBuilder sb)   throws Exception {  // 解析一个节点,比如解析到了一个if节点,假如test判断为true这里就返回true  TempVal val = parserOneElement(currParams, globalParams, ele1, sb);  //得到解析的这个节点的抽象节点对象  BaseNode node = val.getNode();  /**   * 实际上这句之上的语句只是解析了*/  node.pre(currParams, globalParams, ele1, sb);  //判断是否还需要解析节点里的内容,例如if节点test结果为true  boolean flag = val.isContinue();  // 得到该节点下的所有子节点的集合,包含普通文本  List<Node> nodes = ele1.content();  if (flag && !nodes.isEmpty()) {   /**    * 这里表示要进一步解析节点里的内容了,可以把节点类比成一个方法的外壳    * 里面的内容类比成方法里的具体语句,开始解析节点的内容之前    * 先创建本节点下的局部参数的容器,最方便当然是map    */   Map<String, Object> params = new HashMap<String, Object>();   /**    * 把外面传进来的局部参数,直接放入容器,由于本例中参数都是常用数据类型    * 不会存在引用类型所以,这里相当于是一个copy,为了不影响外面传入的对象    * 可以类比方法调用传入参数的情况    */   params.putAll(currParams);   //循环所有子节点   for (int i = 0; i < nodes.size();) {    Node n = nodes.get(i);    //如果节点是普通文本    if (n instanceof Text) {     String text = ((Text) n).getStringValue();     if (StringUtils.isNotEmpty(text.trim())) {      //处理一下文本,如处理#{xx},直接替换${yy}为真实传入的值      sb.append(handText(text, params,globalParams));     }     i++;    } else if (n instanceof Element) {     Element e1 = (Element) n;     // 递归解析     parserElement(e1, params, globalParams, sb);     // 如果循环标志不为true则解析下一个标签     // 这里表示需要重复解析这个循环标签,则i不变,反之继续处理下一个元素     boolean while_flag = MapUtils.getBoolean(params,       Attrs.WHILE_FLAG, false);     if (!while_flag       || !NodeFactory.isWhile(n.getName())       || e1.attributeValue(Attrs.INDEX) == null       || !e1.attributeValue(Attrs.INDEX).equals(         params.get(Attrs.WHILE_INDEX))) {      i++;     }    }   }   //节点处理之后做一些啥事   node.after(currParams, globalParams, ele1, sb);   // 回收当前作用域参数   params.clear();   params = null;  } } /**  * 处理文本替换掉#{item}这种参数  * @param str  * @param params  * @return  * @throws Exception  */ private String handText(String str, Map<String, Object> params,Map<String, Object> 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<String,Object> allParams = getAllParams(params, globalParams);  while(m1.find()) {   String tmpKey = m1.group(2);   String key = whileList == null?tmpKey:(whileList+"_"+tmpKey);   key = index == null?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<String, Object> getAllParams(Map<String, Object> currParams,   Map<String, Object> globalParams) {  Map<String,Object> allParams = new HashMap<String,Object>();  allParams.putAll(globalParams);  allParams.putAll(currParams);  return allParams; }  // 解析一个 private TempVal parserOneElement(Map<String, Object> currParams,   Map<String, Object> globalParams, Element ele, StringBuilder sb)   throws Exception {  //获取  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<String, Object> getParams() {  return currParams; } /**  * 封装一个@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<String, Object> currParams, Map<String, Object> globalParams, Element ele,StringBuilder sb) throws Exception; public void pre(Map<String, Object> currParams,Map<String, Object> globalParams,Element ele,StringBuilder sb) throws Exception { } public void after(Map<String, Object> currParams,Map<String, Object> globalParams,Element ele,StringBuilder sb) throws Exception { } protected Map<String, Object> getAllParams(Map<String, Object> currParams,            Map<String, Object> globalParams) {  Map<String,Object> allParams = new HashMap<String,Object>();  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<String, Object> currParams, Map<String, Object> globalParams, Element ele,StringBuilder sb) throws Exception {  //得到if节点的test属性  String testStr = ele.attributeValue("test");  boolean test = false;  try {   if(StringUtils.isNotEmpty(testStr)) {    //合并全局变量和局部变量    Map<String, Object> 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<String, Object> currParams, Map<String, Object> 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<String, Object> 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<String,Object> 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<Map.Entry<String,Object>> set = map.entrySet();      List<Map.Entry<String,Object>> 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<String, Object> currParams, Map<String, Object> 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<String, Object> currParams, Map<String, Object> 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<String, Object> 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<String, Object> currParams, Map<String, Object> 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<String,BaseNode> nodeMap = new ConcurrentHashMap<String,BaseNode>(); private final static List<String> 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文件

<project ="http://maven.apache.org/POM/4.0.0" ="http://www.w3.org/2001/ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 >> <modelVersion>4.0.0</modelVersion> <groupId>com.rd</groupId> <artifactId>parser</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>myparser</name> <url></url> <dependencies>  <dependency>   <groupId>dom4j</groupId>   <artifactId>dom4j</artifactId>   <version>1.6.1</version>  </dependency>  <dependency>   <groupId>opensymphony</groupId>   <artifactId>ognl</artifactId>   <version>2.6.11</version>  </dependency>  <dependency>   <groupId>commons-collections</groupId>   <artifactId>commons-collections</artifactId>   <version>3.2.1</version>  </dependency>  <dependency>   <groupId>commons-lang</groupId>   <artifactId>commons-lang</artifactId>   <version>2.6</version>  </dependency>  <dependency>   <groupId>junit</groupId>   <artifactId>junit</artifactId>   <version>3.8.1</version>   <scope>test</scope>  </dependency> </dependencies> <build>  <resources>   <resource>    <directory>src/main/java</directory>    <includes>     <include>**/*.</include>    </includes>   </resource>   <resource>    <directory>src/main/resources</directory>    <includes>     <include>**/*</include>    </includes>   </resource>  </resources>  <testResources>   <testResource>    <directory>${project.basedir}/src/test/java</directory>   </testResource>   <testResource>    <directory>${project.basedir}/src/test/resources</directory>   </testResource>  </testResources>  <plugins>   <plugin>    <groupId>org.apache.maven.plugins</groupId>    <artifactId>maven-compiler-plugin</artifactId>    <version>3.1</version>    <configuration>     <source>1.8</source>     <target>1.8</target>     <encoding>UTF-8</encoding>    </configuration>   </plugin>  </plugins> </build></project>

 

 

海外公司注册、海外银行开户、跨境平台代入驻、VAT、EPR等知识和在线办理:https://www.xlkjsw.com

原标题:自己动手实现mybatis动态sql

关键词:sql

sql
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。