Mybatis中SqlMapper配置的扩展与应用详细介绍(1)

奋斗了好几个晚上调试程序,写了好几篇博客,终于建立起了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(?,'1','A','2','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 '1' THEN 'A' WHEN '2' THEN 'B' ELSE 'C' END AS DECODE_TEST from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE '%'||?||'%'

以上所述是小编给大家介绍的Mybatis中SqlMapper配置的扩展与应用详细介绍(1),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Java的MyBatis框架中Mapper映射配置的使用及原理解析

    Mapper的内置方法 model层就是实体类,对应数据库的表.controller层是Servlet,主要是负责业务模块流程的控制,调用service接口的方法,在struts2就是Action.Service层主要做逻辑判断,Dao层是数据访问层,与数据库进行对接.至于Mapper是mybtis框架的映射用到,mapper映射文件在dao层用. 下面是介绍一下Mapper的内置方法: 1.countByExample ===>根据条件查询数量 int countByExample(UserE

  • MyBatis MapperProvider MessageFormat拼接批量SQL语句执行报错的原因分析及解决办法

    最近在项目中有这么一段代码:下载服务器基础业务数据进行本地批量插入操作,因项目中使用mybatis进行持久化操作,故直接考虑使用mybatis的批量插入功能. 1.以下是Mapper接口的部分代码 public interface PrintMapper { @InsertProvider(type = PrintMapperProvider.class,method = "insertAllLotWithVehicleCode4H2") void insertAllLotWithVe

  • mybatis的动态sql详解(精)

    MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力.如果你有使用 JDBC 或其他 相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空 格或在列表的最后省略逗号.动态 SQL 可以彻底处理这种痛苦. 通常使用动态SQL不可能是独立的一部分,MyBatis当然使用一种强大的动态SQL语言来改进这种情形,这种语言可以被用在任意映射的SQL语句中. 动态SQL元素和使用 JSTL或其他相似的基于XML的文本处理器相似.在MyBatis之前的版本中,有很多

  • MyBatis Mapper代理使用方法详解

    MyBatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录. 下文重点给大家介绍mapper代理使用方法. 一.开发人员需要完成的任务: mapper.xml映射文件和mapper.java 二.开发规范

  • 使用Mybatis Generator结合Ant脚本快速自动生成Model、Mapper等文件的方法

    MyBatis简介: MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录. 相关阅读:MyBatis入门学习教程(一)-MyBatis快速入门 使用过Mybatis的同学都知道,针对每一个项目中使用到的数据库表都需要建

  • Mybatis实现Mapper动态代理方式详解

    一.实现原理 Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mapper接口开发需要遵循以下规范: 1.Mapper.xml文件中的namespace与mapper接口的类路径相同. 2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同 3.Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的para

  • 详解MyBatis的getMapper()接口、resultMap标签、Alias别名、 尽量提取sql列、动态操作

    一.getMapper()接口 解析:getMapper()接口 IDept.class定义一个接口, 挂载一个没有实现的方法,特殊之处,借楼任何方法,必须和小配置中id属性是一致的 通过代理:生成接口的实现类名称,在MyBatis底层维护名称$$Dept_abc,selectDeptByNo() 相当于是一个强类型 Eg 第一步:在cn.happy.dao中定义一个接口 package cn.happy.dao; import java.util.List; import cn.happy.e

  • Mybatis增删改查mapper文件写法详解

      1. 插入 <mapper namespace="需要实现接口的全类名"> <insert id="需要实现的接口里的方法名" parameterType="方法参数类型,如果是对象要写全类名"> INSERT sql命令(命令里通过#{}获取对象属性) <!--注意属性名区分大小写 --> </insert> <mapper> EG: <mapper namespace=&q

  • MyBatis实践之DAO与Mapper

    MyBatis简介 MyBatis前身是iBatis,是一个基于Java的数据持久层/对象关系映射(ORM)框架. MyBatis是对JDBC的封装,使开发人员只需关注SQL本身,而不需花费过多的精力去处理如注册驱动.设置参数.创建Connection/Statement.解析结果集等JDBC过程性代码.MyBatis基于XML/注解的方式配置Statement,执行SQL,并将执行结果映射成Java对象, 大大降低了数据库开发的难度. MyBatis is a first class pers

  • oracle+mybatis 使用动态Sql当插入字段不确定的情况下实现批量insert

    最近做项目遇到一个挺纠结的问题,由于业务的关系,DB的数据表无法确定,在使用过程中字段可能会增加,这样在insert时给我造成了很大的困扰. 先来看一下最终我是怎么实现的: <insert id="batchInsertLine" parameterType="HashMap"> <![CDATA[ INSERT INTO tg_fcst_lines(${lineColumn}) select result.*,sq_fcst_lines.next

随机推荐