Mybatis兼容sql

        项目使用mybatis操作数据库。最初支持的数据库是oracle,后来需求要同时支持mysql。问题来了,oracle和mysql或多或少有差别,最初的设计没有考虑到兼容支持多种数据库,设计上严重依赖oracle(比如序列生成主键)。之前《mybatis Oracle到Mysql迁移 记录》有稍微记录了一下,使用的方法是:1)建表和函数|存储过程 模拟oracle的sequence,2)使用spring profile切换配置,一个项目里有2套xml。

组长说这样不行2套xml太麻烦。但是用VendorDatabaseIdProvider另外写有差异的sql又有点乱。说要弄一个规范,执行的时候再按照数据库类型生成sql。那意思就是要做编译,一个自定义的sql语法转换成特定数据库的sql语法?这样有2个地方可以进行修改:一种SqlSessionFactoryBean生产configuration后的mappingstatements里修改,一种mybatis的StatementHandle或Executor 的插件拦截。能力有限写不出性能很好的编译器,使用第二种的话更影响执行速度。第一种的话mappingstatments, sqlNode的字段都是私有需要反射,强行不好。 然后点来点去点来点去点到xml,瞄到动态标签和ognl。白想那么多了,可以使用动态标签静态函数生成特定的sql片段。但是mybatis不支持自定义标签,需要改源码。使用foreach标签比较繁琐还是可以用的<foreach item="item" collection="#{'1': @Generator@nextvalSql('序列名')}">${item}</foreach> (可能还有其他比较简洁的写法,试了几种目前没想到)。最后还是决定自定义一个标签再搭配一些sql转换的静态方法。

 

关于mybatis解析sql过程可以参考Mybatis解析动态sql原理分析 这篇文章。

目标: 自定义标签<value expr="@Generator@nextvalSql('序列名')"/> 返回表达式的字符串。

 

5个步骤

1)修改org.apache.ibatis.builder.xml下的mybatis-3-mapping.dtd,加入value的校验规则,参看include标签的规则定义。下面是片段

<!ELEMENT include EMPTY>
<!ATTLIST include
refid CDATA #REQUIRED
> 
 
<!ELEMENT value EMPTY>
<!ATTLIST value
expr CDATA #REQUIRED
> 
  
<!ELEMENT sql (#PCDATA | include | value | trim | where | set | foreach | choose | if | bind)*>
<!ATTLIST sql
id CDATA #REQUIRED
lang CDATA #IMPLIED
databaseId CDATA #IMPLIED
>

 

2)实现接口org.apache.ibatis.scripting.xmltags.SqlNode  ValueSqlNode 翻译处理value标签。

public class ValueSqlNode implements SqlNode 
{ 
    private final String expression;
 
    public ValueSqlNode(String expression)
    {
       this.expression=expression;
    }
    
    @Override
    public boolean apply(DynamicContext context) 
    {
        context.appendSql(OgnlCache.getValue(expression, context.getBindings()).toString());
        return true;
    }
}

 

3)XMLScriptBuilder添加nodeHandler

  NodeHandler nodeHandlers(String nodeName) 
  {
    Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
    map.put("trim", new TrimHandler());
    map.put("where", new WhereHandler());
    map.put("set", new SetHandler());
    map.put("foreach", new ForEachHandler());
    map.put("if", new IfHandler());
    map.put("choose", new ChooseHandler());
    map.put("when", new IfHandler());
    map.put("otherwise", new OtherwiseHandler());
    map.put("bind", new BindHandler());
    map.put("value",new ValueHandler());
    return map.get(nodeName);
  }
 
  private class ValueHandler implements NodeHandler 
  {
        public ValueHandler() {
          // Prevent Synthetic Access 求问这个何解?
        }
 
        @Override
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
            final String expression = nodeToHandle.getStringAttribute("expr");
            final ValueSqlNode node = new ValueSqlNode(expression);
            targetContents.add(node);
        }
  }

 

4)一些sql转换的静态方法。代码也很简单:Generator.java , Tanslator

 

5)使用,用正则匹配替换原来的sql片段

批量处理插入

<insert id="xx" flushCache="true" parameterType="java.util.List" >

    <value expr="@Generator@batchBegin()"/>  

       <foreach collection="list" item="item" index="index" separator=";" close=";">

           INSERT INTO xxxx(xxx,xxx) values

           (#{item.xxxx},#{item.xxxx})

       </foreach>

<value expr="@Generator@batchEnd()"/>

</insert>

 

字符串连接

JS.SCHEMA_CODE  like  '%'||#{schemaCode}||'%'

改成

JS.SCHEMA_CODE like <value expr="@Generator@concat('\'%\'','#{schemaCode}','\'%\'')"/>

 

关键字处理

T."ORDER"

改成

T.<value expr="@Generator@keywordEscape('ORDER')"/>

  

返回记录个数限制

AND ROWNUM<2

改成

<value expr="@Generator@limit(n,true)"/>

 

ROWNUM<2

改成

<value expr="@Generator@limit(n,false)"/>

 

获取序列下一个值

SEQ_FILE_ID.NEXTVAL

改成

<value expr="@Generator@nextval('SEQ_FILE_ID')"/>

 

获取序列下n个值

SELECT SEQ_OPERATE_PARAMS_ID.NEXTVAL FROM DUAL CONNECT BY LEVEL<=#{n}

改成

<value expr="@Generator@nextvalN('SEQ_OPERATE_PARAMS_ID','#{n}')"/>

 

日期格式

to_date( #{qRuntimeLastBegin},'yyyy-MM-dd HH24:mi:ss')

改成

<value expr="@Generator@strToDate('#{qRuntimeLastBegin}','yyyy-MM-dd HH:mm:ss')"/>

  

to_date( #{qRuntimeLastBegin},'yyyy-MM-dd')

改成

<value expr="@Generator@strToDate('#{qRuntimeLastBegin}','yyyy-MM-dd')"/>

 

当前日期

sysdate

改成

<value expr="@Generator@sysdate()"/>

 

 

 

你可能感兴趣的:(mybatis)