Mybatis中SqlMapper配置的扩展与应用(3)
隔了两周,首先回顾一下,在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的帮助类:

这个工具类分成四个部分:
- 实现ApplicationContextAware接口,注入ApplicationContext(BeanFactory)对象
- 表达式求值方法
- 对表达式简单求值(还可指定返回的目标类型)
- 指定root对象,对表达式求值(还可指定返回的目标类型)
- 指定root对象和其它变量,对表达式求值(还可指定返回的目标类型)
- 表达式设置方法
- 设置表达式的值
- 指定root对象,设置表达式的值
- 指定root对象和其它变量,设置表达式的值
- 变量管理方法
- 添加变量
- 移除变量
此外,还内置了一个保护变量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)的更多相关文章
- Mybatis中SqlMapper配置的扩展与应用(2)
三.子表删除兼容问题 这个问题,使用SQL配置函数不太好处理,而且就算使用SQL配置函数,也不够直观,有点自动生成SQL的意味,太Hibernate了(不过要是可以兼收Hibernate和Mybati ...
- Mybatis中SqlMapper配置的扩展与应用(1)
奋斗了好几个晚上调试程序,写了好几篇博客,终于建立起了Mybatis配置的扩展机制.虽然扩展机制是重要的,然而如果没有真正实用的扩展功能,那也至少是不那么鼓舞人心的,这篇博客就来举几个扩展的例子. 这 ...
- mybatis中resultMap配置细则
resultMap算是mybatis映射器中最复杂的一个节点了,能够配置的属性较多,我们在mybatis映射器配置细则这篇博客中已经简单介绍过resultMap的配置了,当时我们介绍了resultMa ...
- MyBatis中---数据库配置的属性名冲突问题
一.db.properties 属性文件中 最好加特殊的标志前缀 jdbc.username ,如果单纯的username有可能影响到 mapper.xml中的 ${username}; 举例 ...
- mybatis中namespace配置方式
namespace有三种全路径的配置方式: namespace绑定实体类的全路径;绑定dao接口的全路径绑定;mapper的sql.xml文件第一种:namespace绑定实体类的全路径: 当name ...
- MyBatis中的配置错误creating bean with name 'sqlSessionFactory'
错误信息如下: 警告: Exception encountered during context initialization - cancelling refresh attempt: org.sp ...
- Springboot中以配置类方式自定义Mybatis的配置规则(如开启驼峰映射等)
什么是自定义Mybatis的配置规则? 答:即原来在mybatis配置文件中中我们配置到<settings>标签中的内容,如下第6-10行内容: 1 <?xml version=&q ...
- 优化与扩展Mybatis的SqlMapper解析
接上一篇博文,这一篇来讲述怎么实现SchemaSqlMapperParserDelegate——解析SqlMapper配置文件. 要想实现SqlMapper文件的解析,还需要仔细分析一下mybatis ...
- Mybatis中配置Mapper的方法
在这篇文章中我主要想讲一下Mybatis配置文件中mappers元素的配置.关于基础部分的内容可以参考http://haohaoxuexi.iteye.com/blog/1333271. 我们知道在M ...
随机推荐
- Xamarin studio配置问题
最近对Xamarin很感兴趣,就下班抽空在家里的电脑上进行配置,于是乎出现了各种问题,对此进行总结. 1. Cannot find `aapt.exe`. Please install the And ...
- 通过uCGUIBulider4.0建立的ucGUI文件,控件汉字不能显示问题解决办法
由于uCGUIBulider4.0不能在64位操作系统中运行,于是在电脑上通过VMware Workstation Pro搭建虚拟的32位的win7环境,然后把win7中用uCGUIBulider4. ...
- 如何利用word2013写图文并茂的博客
我有一天心血来潮,突然想写博客了,由于是技术贴,图文并茂,多图预警,可是在新浪博客,网易博客,博客园这些博客上写技术贴,似乎都不支持从已经写好的word文档里粘贴过去,于是各种百度各种尝试各种摸索 ...
- Refresh recovery area usage data after manually deleting files under recovery area
Original source: http://www.dba-oracle.com/t_v$_flash_recovery_area.htm If you manually delete files ...
- Hbase随笔2
Hbase是建立在HDFS上的分布式数据库,下图是Hbase表的模型: Hbase这个数据库其实和传统关系数据库还是有很多类似之处,而不是像mongodb,memcached以及redis完全脱离了表 ...
- WCF服务配置问题
上一篇中,我们主要是使用了代码来实现服务的自我寄宿.代码的实现稍微复杂些,不过还有些使用配置文件和配置工具的方法.下面来一一介绍下. 1.配置文件.首先在Host下添加个app.confi ...
- Java的UUID
UUID含义是通用唯一识别码 (Universally Unique Identifier),这 是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OS ...
- (function(){...}())与(function(){...})()
(function(){ ...... }()) 或 (function(){ ...... })() 匿名函数自调用,也就是说,定义一个匿名函数 ...
- EQueue - 详细谈一下消息持久化以及消息堆积的设计
前言 之前写了一篇文章,总体介绍了EQueue.在看这篇文章之前如果还没看过那篇文章,可能会看不懂这篇文章.所以建议没看过的朋友务必先看一下那篇文章中所提到的各种概念,这样才能更好的理解本文所说的内容 ...
- SWT:获取字符串实际宽度
由于SWT取用的是系统文字size,有个简单方式可以获取一整段包含中文\英文\数字\特殊字符的字符串宽度. 即是利用Label的computeSize方法,我们知道Label的大小可以随着内容文字伸缩 ...