隔了两周,首先回顾一下,在Mybatis中的SqlMapper配置文件中引入的几个扩展机制:

1.引入SQL配置函数,简化配置、屏蔽DB底层差异性

2.引入自定义命名空间,允许自定义语句级元素、脚本级元素

3.引入表达式配置,扩充SqlMapper配置的表达能力

前面两条已经举过例子,现在来看看怎么使用表达式配置。说到表达式语言,最为富丽堂皇的自然就是OGNL,但这也正是Mybatis内部访问数据的固有方式,所以也轮不到我们在这里来扩充了(事实上Mybatis的参数设置并不能使用完全的OGNL)。那么,除了OGNL,还有哪些表达式语言呢?别忘了,我们的前提是Spring环境,自然,SpEL表达式也就走入我们的视野,因此这篇文章就重点记录在SqlMapper中使用SpEL表达式

四、在Mybatis中的SqlMapper使用SpEL表达式

1.SpEL工具类

SpEL就是Spring提供的EL表达式,虽然到Spring3才开始推出,但已经是Spring的一个基础核心模块了,地位已经差不多等同于IoC和AOP了。SpEL和OGNL类似,也有表达式、上下文环境、root对象等概念,但和OGNL不同的是,SpEL还提供了访问Spring中bean的能力——这是非常强悍的,试问一个Spring应用有多少类不是Spring管理的呢?具体的SpEL语法细节可以参考Spring的官方文档。

SpEL目前主要应用于Spring的配置,使用起来非常方便,但是在Java类中使用则比较繁琐,稍微实用一点的例子都需要创建解析器实例、创建执行环境、解析表达式、对表达式求值等步骤,如果需要访问Spring的Bean,还要设置BeanFactoryResolver等,因此,为了简化SpEL在Java中的应用,我编写了一个SpEL的帮助类:



这个工具类分成四个部分:

  1. 实现ApplicationContextAware接口,注入ApplicationContext(BeanFactory)对象
  2. 表达式求值方法
    • 对表达式简单求值(还可指定返回的目标类型)
    • 指定root对象,对表达式求值(还可指定返回的目标类型)
    • 指定root对象和其它变量,对表达式求值(还可指定返回的目标类型)
  3. 表达式设置方法
    • 设置表达式的值
    • 指定root对象,设置表达式的值
    • 指定root对象和其它变量,设置表达式的值
  4. 变量管理方法
    • 添加变量
    • 移除变量

此外,还内置了一个保护变量Tool。

编写一个测试类验证一下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"classpath:applicationContext.xml"
})
@Component // 该测试类本身作为一个Spring管理的bean,便于后面的测试
public class SpringHelpTest { public String getBeanValue(String arg){//bean的一个方法
return "beanValue:"+arg;
} @Test
public void testSpelHelp(){
// 准备root对象 {key1 : 'root-value1', key2 : 'root-value2'}
Root root = new Root("root-value1", "root-value2");
// 准备一般变量
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("var1", "value1");
vars.put("var2", "value2");
// 直接计算简单表达式
Object rs = SpringHelp.evaluate("1+2");
Assert.assertEquals(3, rs);
// 按指定类型计算简单表达式
rs = SpringHelp.evaluate("1+2", String.class);
Assert.assertEquals("3", rs);
// 访问root对象的属性
rs = SpringHelp.evaluate(root, "key1");
Assert.assertEquals("root-value1", rs);
// 访问一般变量
rs = SpringHelp.evaluate(root, "#var2", vars);
Assert.assertEquals("value2", rs);
// 访问root对象
rs = SpringHelp.evaluate(root, "#root", vars);
Assert.assertTrue(rs == root);
// 访问Spring管理的bean,同时传入的参数又是root对象的属性
rs = SpringHelp.evaluate(root, "@springHelpTest.getBeanValue(key2)", vars);
Assert.assertEquals("beanValue:root-value2", rs);
// 设置root对象的属性
SpringHelp.setValue(root, "key1", "new-root-value1");
rs = SpringHelp.evaluate(root, "key1");
Assert.assertEquals("new-root-value1", rs);
//访问工具类,其中Tool.DATE.getDate()的作用是获取当前日期
rs = SpringHelp.evaluate("#Tool.DATE.getDate()");
Assert.assertEquals(Tool.DATE.getDate(), rs);
} public class Root{
String key1;
String key2;
Root(String key1, String key2){
this.key1 = key1;
this.key2 = key2;
}
// 省略getter/setter方法
}
}

有了这个静态帮助类,在Java中使用SpEL就方便很多了。

2.编写表达式处理器

利用SpEL帮助类,编写表达式处理器IExpressionHandler的实现,具体逻辑参看代码中的注释

public class SpelExpressionHandler implements IExpressionHandler {

	/**
* 直接返回true,也就是说不做进一步判断,支持所有的${(exp)}、#{(exp)}内的表达式
* 由于支持所有表达式,实际上起到了一种拦截作用,所以需要注意,注册该实现时必须最低优先级
*/
@Override
public boolean isSupport(String expression) {
return true;
} /**
* 对SqlMapper配置中的表达式求值
*/
@Override
public Object eval(String expression, Object parameter, String databaseId) {
/**
* 如果以spel:为前缀,则将mybatis包装后的参数、数据库id以及表达式自身一起封装一个新的root对象
* 因此在exp表达式中可以通过params.paramName、databaseId等形式访问
*/
if(expression.toLowerCase().startsWith("spel:")){
expression = expression.substring(5);
Root root = new Root(parameter, databaseId, expression);
return SpringHelp.evaluate(root, expression);
}
/**
* 否则将databaseId作为一个特殊名称的变量
* 因此在exp表达式中可以通过paramName、#databaseId等形式访问
*/
else{
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("databaseId", databaseId);
return SpringHelp.evaluate(parameter, expression, vars);
}
} public class Root { private final Object params;
private final String databaseId;
private final String expression; public Root(Object params, String databaseId, String expression) {
this.params = params;
this.databaseId = databaseId;
this.expression = expression;
} // 省略getter/setter方法
}
}

3.注册表达式处理器

如上面的注释,注册的时候需要注意一点,优先级要最低,以避免所有表达式都被拦截,导致其它的处理器不生效。

保证优先级最低,有一种方法,就是实现Spring中的Order接口,并且将该实现类的order值设置为最大,然后按Order排序;另外一种方法,就是干脆另起炉灶,单独一个属性保存默认处理器,只有其它处理器都不支持的时候才使用默认处理器,请看下面的代码:

/**
* 表达式处理器
*/
private static final Set<IExpressionHandler> expressions = new LinkedHashSet<IExpressionHandler>();
/**
* 默认表达式处理器
*/
private static final IExpressionHandler defaultExpressionHandler = new SpelExpressionHandler();
/**
* 获取表达式处理器
* @param node
* @return
*/
public static IExpressionHandler getExpressionHandler(String expression){
for(IExpressionHandler handler : expressions){
if(handler.isSupport(expression)){
return handler;
}
}
return defaultExpressionHandler;
}

4.修改SqlMapper中配置

<?xml version="1.0" encoding="UTF-8" ?>
<mapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://dysd.org/schema/sqlmapper"
xmlns:e="http://dysd.org/schema/sqlmapper-extend"
xsi:schemaLocation="http://dysd.org/schema/sqlmapper http://dysd.org/schema/sqlmapper.xsd
http://dysd.org/schema/sqlmapper-extend http://dysd.org/schema/sqlmapper-extend.xsd"
namespace="org.dysd.dao.mybatis.mapper.IExampleDao"> <select id="selectString" resultType="string">
select PARAM_NAME, ${(@spelBean.param(paramName))} AS TEST_SPEL
from BF_PARAM_ENUM_DEF
where PARAM_NAME $like{#{(spel:@spelBean.root(#root,params.paramName)), jdbcType=VARCHAR}}
order by SEQNO
</select>
</mapper>

5.编写配置中的bean

@Component("spelBean")
public class SpelBean { public String param(String paramName){
// 测试的是${()},所以返回结果中添加单引号
return "'PARAM-"+paramName+"'";
} public String root(SpelExpressionHandler.Root root,String paramName){
// 测试spel:为前缀的表达式,所以可以直接访问SpelExpressionHandler.Root对象
return "ROOT-"+root.getDatabaseId()+"-"+paramName;
}
}

6.编写Dao接口

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

7.编写JUnit测试类

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

8.执行测试

20161119 19:00:44,298 [main]-[DEBUG] ==>  Preparing: select PARAM_NAME, 'PARAM-DISPLAY_AREA' AS TEST_SPEL from BF_PARAM_ENUM_DEF where PARAM_NAME LIKE CONCAT('%',?,'%') order by SEQNO
20161119 19:00:48,001 [main]-[DEBUG] ==> Parameters: ROOT-MySQL-DISPLAY_AREA(String)

可以看到,无论是${(exp)}还是#{(exp)},其中的exp都已经得到正确的解析了。

在SqlMapper中可以调用Spring的Bean,大大丰富了SqlMapper的表达能力,但是对于${(exp)}这种情形,由于是字符串的简单替换,也存在SQL注入的风险,因此一般只使用#{(exp)}。

题外话:

1.SqlMapper的扩展与应用系列算是暂告一段落,有朋友希望我能提供实际的案例,我利用这两周的业余时间整理了一下,在GitHub和OSChina同步上传了这个项目,有兴趣的朋友可以看一下,也希望可以多提一点建议给我。因为是maven项目,希望实际运行的朋友最好搭建一个nexus私服,然后git下载,导入至Eclipse中,修改数据库配置即可。

具体地址:

GitHub:https://github.com/linjisong/dysd

OSChina:https://git.oschina.net/linjisong/dysd

2.在博客园中首次使用Markdown,好多地方还不熟悉,比如代码折叠,但也算是一种新的尝试。

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

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

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

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

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

  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. Xcode自定义Eclipse中常用的快捷键

    转载自http://joeyio.com/2013/07/22/xcode_key_binding_like_eclipse/ Xcode自定义Eclipse中常用的快捷键 22 July 2013 ...

  2. Python 学习---------Day6

    18章 模块:宏伟蓝图 import 使客户端(导入者)以一个整体获取一个模块 from 允许客户端从一个模块文件中获取特定的变量名 reload 在不中止Python程序的情况下,提供了一种重新载入 ...

  3. Websocket Component

    As of Camel 2.10, the Websocket component supports SSL/TLS configuration through the Camel JSSE Conf ...

  4. ServiceMix in daemon mode

    For development simplicity, we can start Karaf in daemon mode by executing 'bin\admin.bat start root ...

  5. iOS一些关于日历的问题

    int CalculateDays(int ys, int ms, int ds, int ye, int me, int de) { int days = CalcYearRestDays(ys, ...

  6. html+css实现简易下拉菜单

    <!DOCTYPE html> <html> <head> <style> div { width:100px; height:40px; overfl ...

  7. 【设计模式之单例模式InJava】

    1. 单例模式 1.1饿汉式(开发常用) class SingleFirst { /* 添加其他成员信息 */ private static SingleFirst s1 = new SingleFi ...

  8. iOS 时间戳的转换

    在开发iOS程序时,有时候需要将时间格式调整成自己希望的格式,这个时候我们可以用NSDateFormatter类来处理.例如: //实例化一个NSDateFormatter对象 NSDateForma ...

  9. ubuntu vps折腾记

    买了burgetVM的vps,512M内存/1024M交换内存,40G硬盘,2TB流量/月,cpu xeon E5-2620 操作系统选择了ubuntu 12,开始折腾. 第一步,配置vpn 找了很多 ...

  10. vs2010 和vs2012的区别 副标题--Loaded事件走两次

    我上一遍博文没有通过首页显示!这边就简短的描述一下问题,希望大拿们有遇到类似问题或者知道原因的回答一下下!!! 最终的问题是Loaded事件走两次,具体可以看我上一篇对问题的描述. 在目标框架同样都是 ...