奋斗了好几个晚上调试程序,写了好几篇博客,终于建立起了Mybatis配置的扩展机制。虽然扩展机制是重要的,然而如果没有真正实用的扩展功能,那也至少是不那么鼓舞人心的,这篇博客就来举几个扩展的例子。

这次研读源码的起因是Oracle和MySQL数据库的兼容性,比如在Oracle中使用双竖线作为连接符,而MySQL中使用CONCAT函数;比如Oracle中可以使用DECODE函数,而MySQL中只能使用标准的CASE WHEN;又比如Oracle中可以执行DELETE FORM TABLE WHERE FIELD1 IN (SELECT FIELD1 FORM TABLE WHERE FIELD2=?),但是MySQL中会抛出异常,等等。下面就从解决这些兼容性问题开始,首先需要在配置中添加数据库标识相关的配置:

<!-- 自行构建Configuration对象 -->
<bean id="mybatisConfig" class="org.dysd.dao.mybatis.schema.SchemaConfiguration"/>
<bean id="sqlSessionFactory" p:dataSource-ref="dataSource"
class="org.dysd.dao.mybatis.schema.SchemaSqlSessionFactoryBean">
<!-- 注入mybatis配置对象 -->
<property name="configuration" ref="mybatisConfig"/>
<!-- 自动扫描SqlMapper配置文件 -->
<property name="mapperLocations">
<array>
<value>classpath*:**/*.sqlmapper.xml</value>
</array>
</property>
<!-- 数据库产品标识配置 -->
<property name="databaseIdProvider">
<bean class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
<property name="properties">
<props>
<!-- 意思是如果数据库产品描述中包含关键字MYSQL,则使用mysql作为Configuration中的databaseId,mybatis原生的实现关键字区分大小写,我没有测试Oracle和DB2 -->
<prop key="MySQL">mysql</prop>
<prop key="oracle">oracle</prop>
<prop key="H2">h2</prop>
<prop key="db2">db2</prop>
</props>
</property>
</bean>
</property>
</bean>

一、连接符问题

1、编写SQL配置函数实现类

public class ConcatSqlConfigFunction extends AbstractSqlConfigFunction{//抽象父类中设定了默认的order级别

    @Override
public String getName() {
return "concat";
} @Override
public String eval(String databaseId, String[] args) {
if(args.length < 2){
Throw.throwException("the concat function require at least two arguments.");
}
if("mysql".equalsIgnoreCase(databaseId)){
return "CONCAT("+Tool.STRING.join(args, ",")+")";
}else{
return Tool.STRING.join(args, "||");
}
}
}

2、在SchemaHandlers类的静态代码块中注册,或者在启动初始化类中调用SchemaHandlers的方法注册

static {
//注册默认命名空间的StatementHandler
register("cache-ref", new CacheRefStatementHandler());
register("cache", new CacheStatementHandler());
register("parameterMap", new ParameterMapStatementHandler());
register("resultMap", new ResultMapStatementHandler());
register("sql", new SqlStatementHandler());
register("select|insert|update|delete", new CRUDStatementHandler()); //注册默认命名空间的ScriptHandler
register("trim", new TrimScriptHandler());
register("where", new WhereScriptHandler());
register("set", new SetScriptHandler());
register("foreach", new ForEachScriptHandler());
register("if|when", new IfScriptHandler());
register("choose", new ChooseScriptHandler());
//register("when", new IfScriptHandler());
register("otherwise", new OtherwiseScriptHandler());
register("bind", new BindScriptHandler()); // 注册自定义命名空间的处理器
registerExtend("db", new DbStatementHandler(), new DbScriptHandler()); // 注册SqlConfigFunction
register(new DecodeSqlConfigFunction());
register(new ConcatSqlConfigFunction()); // 注册SqlConfigFunctionFactory
register(new LikeSqlConfigFunctionFactory());
}

上面代码除了注册ConcatSQLConfigFunction外,还有一些其它的注册代码,这里一并给出,下文将省略。

3、修改SqlMapper配置

<select id="selectString" resultType="string">
select PARAM_NAME, $concat{PARAM_CODE, PARAM_NAME} AS CODE_NAME
from BF_PARAM_ENUM_DEF
<if test="null != paramName and '' != paramName">
where PARAM_NAME LIKE $CONCAT{'%', #{paramName, jdbcType=VARCHAR}, '%'}
</if>
</select>

4、编写dao接口类

@Repository
public interface IExampleDao { public String selectString(@Param("paramName")String paramName);
}

5、编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"classpath:spring/applicationContext.xml"
})
@Component
public class ExampleDaoTest { @Resource
private IExampleDao dao; @Test
public void testSelectString(){
String a = dao.selectString("显示");
Assert.assertEquals("显示区域", a);
}
}

6、分别在MySQL和H2中运行如下(将mybatis日志级别调整为TRACE)

(1)MySQL

20161108 00:12:55,235 [main]-[DEBUG] ==>  Preparing: select PARAM_NAME, CONCAT(PARAM_CODE,PARAM_NAME) AS CODE_NAME from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE CONCAT('%',?,'%')
20161108 00:12:55,269 [main]-[DEBUG] ==> Parameters: 显示(String)
20161108 00:12:55,287 [main]-[TRACE] <== Columns: PARAM_NAME, CODE_NAME
20161108 00:12:55,287 [main]-[TRACE] <== Row: 显示区域, DISPLAY_AREA显示区域
20161108 00:12:55,289 [main]-[DEBUG] <== Total: 1

(2)H2

20161108 00:23:08,348 [main]-[DEBUG] ==>  Preparing: select PARAM_NAME, PARAM_CODE||PARAM_NAME AS CODE_NAME from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%'||?||'%'
20161108 00:23:08,364 [main]-[DEBUG] ==> Parameters: 显示(String)
20161108 00:23:08,411 [main]-[TRACE] <== Columns: PARAM_NAME, CODE_NAME
20161108 00:23:08,411 [main]-[TRACE] <== Row: 显示区域, DISPLAY_AREA显示区域
20161108 00:23:08,411 [main]-[DEBUG] <== Total: 1

可以看到,已经解决连接符的兼容性问题了。

另外,我们也发现,使用LIKE关键字时,写起来比较麻烦,那我们就给它一组新的SQL配置函数吧:

public class LikeSqlConfigFunctionFactory implements ISqlConfigFunctionFactory{

    @Override
public Collection<ISqlConfigFunction> getSqlConfigFunctions() {
return Arrays.asList(getLeftLikeSqlConfigFunction(),getRightLikeSqlConfigFunction(),getLikeSqlConfigFunction());
} private ISqlConfigFunction getLeftLikeSqlConfigFunction(){
return new AbstractLikeSqlConfigFunction(){
@Override
public String getName() {
return "llike";
} @Override
protected String eval(String arg) {
return "LIKE $concat{'%',"+arg+"}";
}
};
} private ISqlConfigFunction getRightLikeSqlConfigFunction(){
return new AbstractLikeSqlConfigFunction(){
@Override
public String getName() {
return "rlike";
} @Override
protected String eval(String arg) {
return "LIKE $concat{"+arg+", '%'}";
}
};
} private ISqlConfigFunction getLikeSqlConfigFunction(){
return new AbstractLikeSqlConfigFunction(){
@Override
public String getName() {
return "like";
} @Override
protected String eval(String arg) {
return "LIKE $concat{'%',"+arg+", '%'}";
}
};
} private abstract class AbstractLikeSqlConfigFunction extends AbstractSqlConfigFunction{
@Override
public String eval(String databaseId, String[] args) {
if(args.length != 1){
Throw.throwException("the like function require one and only one argument.");
}
return eval(args[0]);
}
protected abstract String eval(String arg);
}
}

这里,定义了一组SQL配置函数,左相似,右相似以及中间相似匹配,并且SQL配置函数还可以嵌套。于是,SqlMapper的配置文件简化为:

<select id="selectString" resultType="string">
select PARAM_NAME, $concat{PARAM_CODE, PARAM_NAME} AS CODE_NAME
from BF_PARAM_ENUM_DEF
<if test="null != paramName and '' != paramName">
where PARAM_NAME $like{#{paramName, jdbcType=VARCHAR}}
</if>
</select>

运行结果完全相同。

如果还觉得麻烦,因为PARAM_NAME和paramName是驼峰式对应,甚至还可以添加一个fieldLike函数,并将配置修改为

where  $fieldLike{#{PARAM_NAME, jdbcType=VARCHAR}}

如果再结合数据字典,jdbcType的配置也可自动生成:

where  $fieldLike{#{PARAM_NAME}}

这种情形下,如果有多个参数,也不会出现歧义(或者新定义一个配置函数$likes{}消除歧义),于是可将多个条件简化成:

where  $likes{#{PARAM_NAME, PARAM_NAME2, PARAM_NAME3}}

当然,还有更多可挖掘的简化,已经不止是兼容性的范畴了,这里就不再进一步展开了。

二、DECODE函数/CASE ... WHEN

Oracle中的DECODE函数非常方便,语法如下:

DECODE(条件,值1,返回值1,值2,返回值2,...值n,返回值n[,缺省值])

等价的标准写法:

CASE 条件
WHEN 值1 THEN 返回值1
WHEN 值2 THEN 返回值2
...
WHEN 值n THEN 返回值n
[ELSE 缺省值]
END

现在我们来实现一个$decode配置函数:

public class DecodeSqlConfigFunction extends AbstractSqlConfigFunction{

    @Override
public String getName() {
return "decode";
} @Override
public String eval(String databaseId, String[] args) {
if(args.length < 3){
Throw.throwException("the decode function require at least three arguments.");
}
if("h2".equalsIgnoreCase(databaseId)){//测试时,使用h2代替oracle,正式程序中修改为oracle
return "DECODE("+Tool.STRING.join(args, ",")+")";
}else{
StringBuffer sb = new StringBuffer();
sb.append("CASE ").append(args[0]);
int i=2, l = args.length;
for(; i < l; i= i+2){
sb.append(" WHEN ").append(args[i-1]).append(" THEN ").append(args[i]);
}
if(i == l){//结束循环时,两者相等说明最后一个参数未使用
sb.append(" ELSE ").append(args[l-1]);
}
sb.append(" END");
return sb.toString();
}
}
}

然后使用SchemaHandlers注册,修改SqlMapper中配置:

<select id="selectString" resultType="string">
select PARAM_NAME, $decode{#{paramName}, '1', 'A', '2', 'B','C'} AS DECODE_TEST
from BF_PARAM_ENUM_DEF
<if test="null != paramName and '' != paramName">
where PARAM_NAME $like{#{paramName, jdbcType=VARCHAR}}
</if>
</select>

测试如下:

(1)H2中(以H2代替Oracle)

20161108 06:53:29,747 [main]-[DEBUG] ==>  Preparing: select PARAM_NAME, DECODE(?,'','A','','B','C') AS DECODE_TEST from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%'||?||'%' 

(2)MySQL中

20161108 06:50:55,998 [main]-[DEBUG] ==>  Preparing: select PARAM_NAME, CASE ? WHEN '' THEN 'A' WHEN '' THEN 'B' ELSE 'C' END AS DECODE_TEST from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%'||?||'%' 

Mybatis中SqlMapper配置的扩展与应用(1)的更多相关文章

  1. Mybatis中SqlMapper配置的扩展与应用(3)

    隔了两周,首先回顾一下,在Mybatis中的SqlMapper配置文件中引入的几个扩展机制: 1.引入SQL配置函数,简化配置.屏蔽DB底层差异性 2.引入自定义命名空间,允许自定义语句级元素.脚本级 ...

  2. Mybatis中SqlMapper配置的扩展与应用(2)

    三.子表删除兼容问题 这个问题,使用SQL配置函数不太好处理,而且就算使用SQL配置函数,也不够直观,有点自动生成SQL的意味,太Hibernate了(不过要是可以兼收Hibernate和Mybati ...

  3. mybatis中resultMap配置细则

    resultMap算是mybatis映射器中最复杂的一个节点了,能够配置的属性较多,我们在mybatis映射器配置细则这篇博客中已经简单介绍过resultMap的配置了,当时我们介绍了resultMa ...

  4. MyBatis中---数据库配置的属性名冲突问题

    一.db.properties 属性文件中 最好加特殊的标志前缀  jdbc.username ,如果单纯的username有可能影响到 mapper.xml中的 ${username}; 举例   ...

  5. mybatis中namespace配置方式

    namespace有三种全路径的配置方式: namespace绑定实体类的全路径;绑定dao接口的全路径绑定;mapper的sql.xml文件第一种:namespace绑定实体类的全路径: 当name ...

  6. MyBatis中的配置错误creating bean with name 'sqlSessionFactory'

    错误信息如下: 警告: Exception encountered during context initialization - cancelling refresh attempt: org.sp ...

  7. Springboot中以配置类方式自定义Mybatis的配置规则(如开启驼峰映射等)

    什么是自定义Mybatis的配置规则? 答:即原来在mybatis配置文件中中我们配置到<settings>标签中的内容,如下第6-10行内容: 1 <?xml version=&q ...

  8. 优化与扩展Mybatis的SqlMapper解析

    接上一篇博文,这一篇来讲述怎么实现SchemaSqlMapperParserDelegate——解析SqlMapper配置文件. 要想实现SqlMapper文件的解析,还需要仔细分析一下mybatis ...

  9. Mybatis中配置Mapper的方法

    在这篇文章中我主要想讲一下Mybatis配置文件中mappers元素的配置.关于基础部分的内容可以参考http://haohaoxuexi.iteye.com/blog/1333271. 我们知道在M ...

随机推荐

  1. Microsoft.Office.Interop.Excel 程序集引用 ,Microsoft.Office.Interop.Excel.ApplicationClass 无法嵌入互操作类型

    using Microsoft.Office.Interop.Excel   添加程序集引用 方法:在引用--程序集--扩展中,添加引用Microsoft.Office.Interop.Excel,此 ...

  2. Ubuntu下设置(增加/删除)开机启动项

    As said above, you have to edit /etc/xdg/autostart/ and either: remove the NoDisplay=true lines; or ...

  3. hdu 2896 AC自动机

    // hdu 2896 AC自动机 // // 题目大意: // // 给你n个短串,然后给你q串长字符串,要求每个长字符串中 // 是否出现短串,出现的短串各是什么 // // 解题思路: // / ...

  4. 用php创建mysql数据库

    接触php就等于向后台更近了一步,之前一直在做前端,不过也在学php,但一直没敢写博客,现在终于有勇气迈向了这一步,还请各位博友多多担待. 服务器是后台开发的必备工具,但对于一般初学者来说是没有自己的 ...

  5. HttpCookie加匿名类实现多语言

    突然想做一个多语言网站,确不知道怎么实现好,突然想到了HttpCookie,然后页面后台用匿名类实现语言的储存. string lan = Request["str_lan"]; ...

  6. 常用正则表达式-copy

    匹配中文:[\u4e00-\u9fa5] 英文字母:[a-zA-Z] 数字:[0-9] 匹配中文,英文字母和数字及_: ^[\u4e00-\u9fa5_a-zA-Z0-9]+$ 同时判断输入长度:[\ ...

  7. [Xamarin] 取得所有已安裝軟體清單 (转帖)

    最近會用到,簡單記錄一下,抓取所有該手機已經安裝的軟體清單 結果圖: 首先介紹一下Layout :  \Resources\Layout\Main.axml <?xml version=&quo ...

  8. UML-用例图

    用例图是指由参与者.用例以及它们之间的关系构成的用于描述系统功能的视图.用例图是被称为参与者的外部用户所能观察到的系统功能的模型图,呈现了一些参与者和一些用例,以及它们之间的关系,主要用于对系统.子系 ...

  9. Asp.net下使用HttpModule模拟Filter,实现权限控制

    在asp.net中,我们为了防止用户直接从Url中访问指定的页面而绕过登录验证,需要给每个页面加上验证,或者是在模板页中加上验证.如果说项目比较大的话,添加验证是一件令人抓狂的事情,本次,我就跟大家分 ...

  10. 谈谈javascript语法里一些难点问题(二)

    3)    作用域链相关的问题 作用域链是javascript语言里非常红的概念,很多学习和使用javascript语言的程序员都知道作用域链是理解javascript里很重要的一些概念的关键,这些概 ...