zoukankan      html  css  js  c++  java
  • 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的帮助类:

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

    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,好多地方还不熟悉,比如代码折叠,但也算是一种新的尝试。

  • 相关阅读:
    null in ABAP and nullpointer in Java
    SAP ABAP SM50事务码和Hybris Commerce的线程管理器
    Hybris service layer和SAP CRM WebClient UI架构的横向比较
    SAP ABAP和Linux系统里如何检查网络传输的数据量
    SAP CRM WebClient UI和Hybris的controller是如何被调用的
    SAP CRM和Cloud for Customer订单中的业务伙伴的自动决定机制
    SAP CRM WebClient UI和Hybris CommerceUI tag的渲染逻辑
    SAP BSP和JSP页面里UI元素的ID生成逻辑
    微信jsapi支付
    微信jsapi退款操作
  • 原文地址:https://www.cnblogs.com/linjisong/p/6080834.html
Copyright © 2011-2022 走看看