`
wyuxiao729
  • 浏览: 34566 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

通过Mybatis拦截器巧妙实现通用查询打破实体与字段对应关系

阅读更多
最近两天项目需求研究了一下mybatis拦截器。对于Mybatis拦截器发现其功能强大,虽很灵活但是其内部对象转换太麻烦很多接口没有完全暴露出来。甚至不得不通过反射的方式去取其内部关联对象。可能Mybatis也不希望用户直接对其内部Statement,以及ResultSetHandler等进行操作。那这样与直接JDBC又有何区别呢?

通用查询其实也并非完全通用。只能是稍微的简化一下代码,减少程序员一些重复的工作罢了。本项目采用springMVC + Mybatis + EasyUi 进行构建。设想一种应用场景。我一个统计查询:统计四张表里不同数据,或者多表关联查询:从A表当中查询三个字段,从B表当中查询二个字段,从C表当中查询一个字段,从D表当中查询两个字段。这种场景对于Mybaits来说。几张表的关联查询比较头痛。两种方式,一种是建立实体对象(VO)多表关联查询然后建立映射。返回其VO实体类。另一种方式通过对象关联的方式,Mapper.xml里进行配置。两种方式在此不作讨论。(PS:如有高手有更好的方式解决这种场景请不吝赐教)

本文主要讨论通过Mybatis拦截器实现直接取其 ResultSet 通过约定的SQL语句格式解析后生成数据格式。或者直接JSON化传给前台以作展示。

拦截器:

    由于我们不关心对象与字段的映射关联。所以我们只需要在 ResultSetHandler 当中进行拦截就行了,拦截其handleResultSets方法。

直接上代码:


/*
 * E2ESQM-W 业务端故障诊断系统 
 * FileName:MybatisPageInterceptor.java
 * Company: ZZNode Technology Co., Ltd.
 */
package com.zznode.e2esqm.core.commons;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

/**
 * Mybatis直接返回JSON格式拦截组件
 * @author wangkaiping
 * @version V1.0, 2013-5-17 下午11:43:16
 */
@Intercepts( {@Signature(method = "handleResultSets", type = ResultSetHandler.class, args = {Statement.class}) })
public class MybatisJsonInterceptor implements Interceptor{
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
		BoundSql boundsql = (BoundSql) ReflectUtil.getFieldValue(resultSetHandler, "boundSql");
		String sql = boundsql.getSql();
		if(sql.indexOf("t_sys_privilege ch") != -1){ //测试代码写死的    这里应该根据SQL特殊标识进行解析
			String subSql = sql.substring(6, sql.indexOf("from"));
			System.out.println(subSql); // 解析字段格式为:  t.id as ID,t.name as 名称,t.age as 年龄 
			String [] colmns = subSql.split(",");
			List<String> colmnsArr = new ArrayList<String>(); //字段别名集合。
			for(String i : colmns){
				String [] asName = i.split("as");
				colmnsArr.add(asName[1]);
			}
			
			Statement statement = (Statement) invocation.getArgs()[0]; //取得方法的参数Statement
			ResultSet rs = statement.getResultSet(); // 取得结果集
			List<Map> list = new ArrayList<Map>(); // 方法要求返回一个List  list里装的是K,V的键值对。 K字段别名V值 以便后续JSON化前台直接展示
			while(rs.next()){
				if(colmnsArr.size() >0) {
					Map<String,Object> map = new HashMap<String,Object>();
					for(int i=0 ;i<colmnsArr.size();i++){
						Object obj = rs.getObject(colmnsArr.get(i).trim());
						map.put(colmnsArr.get(i).trim(), obj); //取得结果集后K、V关联后放到MAP当中
					}
					list.add(map);
				}
			}
			return list;//这里直接返回,不要再去invocation.proceed();
		}
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this); 
	}

	@Override
	public void setProperties(Properties properties) {
		System.out.println(properties.getProperty("databaseType")); 
	}
	
    
	/**
	 * 反射工具类
	 * @author wangkaiping
	 * @version V1.0, 2013-5-17 下午11:58:50
	 */
	private static class ReflectUtil {
		
		/**
		 * 利用反射获取指定对象的指定属性
		 * @param obj 目标对象
		 * @param fieldName 目标属性
		 * @return 目标属性的值
		 */
		public static Object getFieldValue(Object obj, String fieldName) {
			Object result = null;
			Field field = ReflectUtil.getField(obj, fieldName);
			if (field != null) {
				field.setAccessible(true);
				try {
					result = field.get(obj);
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			return result;
		}

		/**
		 * 利用反射获取指定对象里面的指定属性
		 * @param obj 目标对象
		 * @param fieldName 目标属性
		 * @return 目标字段
		 */
		private static Field getField(Object obj, String fieldName) {
			Field field = null;
			for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz
					.getSuperclass()) {
				try {
					field = clazz.getDeclaredField(fieldName);
					break;
				} catch (NoSuchFieldException e) {
					// 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
				}
			}
			return field;
		}

		/**
		 * 利用反射设置指定对象的指定属性为指定的值
		 * @param obj 目标对象
		 * @param fieldName 目标属性
		 * @param fieldValue 目标值
		 */
		public static void setFieldValue(Object obj, String fieldName,String fieldValue) {
			Field field = ReflectUtil.getField(obj, fieldName);
			if (field != null) {
				try {
					field.setAccessible(true);
					field.set(obj, fieldValue);
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}  

}



Mybatis 整合 Spring 配置代码 。  这里我们重写了SqlSessionFactoryBean。所以这里用的是自己的。其中我们把我们的拦截器直接注入进去了。

一个是分页的拦截器,一个是上面的我们的直接返回JSON格式的拦截器。


<bean id="myBatisPageIntercept" class="com.zznode.e2esqm.core.commons.MybatisPageInterceptor">
    	<property name="databaseType" value="oracle"></property>
    </bean>
    <bean id="myBatisJsonIntercept" class="com.zznode.e2esqm.core.commons.MybatisJsonInterceptor"></bean>
   	<!-- SqlSessionFactory -->
	<bean id="sqlSessionFactory" class="com.zznode.e2esqm.core.commons.PackagesSqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<!-- 根据实际情况修改或添加多个 -->
		<property name="typeAliasesPackage" value="com.zznode.e2esqm.**.entity" />
		<property name="plugins">
			<list>
                                <!-- 注入拦截器--> 
				<ref bean="myBatisPageIntercept"/>
				<ref bean="myBatisJsonIntercept"/>
			</list>
		</property>
	</bean> 




重写的SqlSessionFactoryBean  代码如下 :


package com.zznode.e2esqm.core.commons;

import java.util.List;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.log4j.Logger;
import org.mybatis.spring.SqlSessionFactoryBean;

import com.zznode.e2esqm.utils.PackageUtils;
import com.zznode.e2esqm.utils.Utils;

/**
 * Spring Mybatis整合
 * 通过通配符方式配置typeAliasesPackage
 * @author sunjian  
 * @version 1.0 2013-2-25
 */
public class PackagesSqlSessionFactoryBean extends SqlSessionFactoryBean{
	
	private final static Logger log = Logger.getLogger(PackagesSqlSessionFactoryBean.class);
	
	@Override
	public void setTypeAliasesPackage(String typeAliasesPackage) {
		List<String> list = PackageUtils.getPackages(typeAliasesPackage);
		if(list!=null&&list.size()>0){
			super.setTypeAliasesPackage(Utils.join(list.toArray(), ","));
		}else{
			log.warn("参数typeAliasesPackage:"+typeAliasesPackage+",未找到任何包");
		}
	}

	@Override
	public void setPlugins(Interceptor[] plugins) {
		// TODO Auto-generated method stub
		super.setPlugins(plugins);
	}
}



Mapper接口测试代码  注意SQL语句每个字段必须写,以as别名的方式生成JSON的 KEY

方法 :

@Select("select ch.id as ID,ch.name as 名称,ch.uri as URI,ch.icon as 图标,ch.description as 描述,ch.ord as 排序号")
public List testPri();


测试返回结果 :

[{图标=pencil, 排序号=12, 名称=角色管理, 状态=1, ID=4, 父ID=1, 描述=角色管理, 类型=1, URI=role/list.do}, {图标=pictures, 排序号=21, 名称=ITV测试, 状态=1, ID=5, 父ID=3, 描述=ITV测试, 类型=1, URI=itvTestAction.do?list}, {图标=pictures, 排序号=14, 名称=菜单权限, 状态=1, ID=8, 父ID=1, 描述=菜单权限, 类型=1, URI=privilege/toList.do}, {图标=pie, 排序号=13, 名称=部门管理, 状态=1, ID=6, 父ID=1, 描述=部门管理 , 类型=1, URI=department/department.do}, {图标=pie, 排序号=2, 名称=系统日志, 状态=1, ID=61, 父ID=60, 描述=系统日志, 类型=1, URI=systemLog/list.do}, {图标=pie, 排序号=3232, 名称=主界面, 状态=1, ID=10, 父ID=5, 描述=管理界面所有功能, 类型=2, URI=panel/*}, {图标=pie, 排序号=3232, 名称=权限功能, 状态=1, ID=11, 父ID=3, 描述=权限所有功能, 类型=2, URI=privilege/*}, {图标=pie, 排序号=3232, 名称=所有权限, 状态=1, ID=12, 父ID=3, 描述=42433424, 类型=2, URI=*}, {图标=pie, 排序号=2, 名称=全局参数, 状态=1, ID=62, 父ID=1, 描述=系统全局参数, 类型=1, URI=sysParameter/toList.do}, {图标=pencil, 排序号=1, 名称=系统管理, 状态=1, ID=1, 父ID=-1, 描述=系统管理, 类型=1, URI=#}, {图标=pie, 排序号=11, 名称=用户管理, 状态=1, ID=2, 父ID=1, 描述=用户管理, 类型=1, URI=user/list.do}, {图标=folder, 排序号=2, 名称=ITV测试, 状态=1, ID=3, 父ID=-1, 描述=ITV测试, 类型=1, URI=#}, {图标=pie, 排序号=31, 名称=日志管理, 状态=1, ID=60, 父ID=-1, 描述=日志管理, 类型=1, URI=systemLog/list.do}]

这里只是我的测试代码,还有很多需要完善

这里只是关于Mybatis实现一些需要关联或者多表联合查询,统计呀等类型查询方式的 一种解决方案。


分享到:
评论

相关推荐

    mybatis拦截器实现通用权限字段添加的方法

    MyBatis拦截器实现通用权限字段添加的方法 MyBatis拦截器是一种非常实用的技术,可以用来实现各种复杂的数据库...通过使用MyBatis拦截器,我们可以轻松地实现通用权限字段添加,达到灵活、可靠、可维护的数据库操作。

    MyBatis拦截器分页与动态修改SQL及其参数值

    在MyBatis框架中,拦截器(Interceptor)是一种强大的工具,可以用来在SQL执行前后进行扩展,例如实现分页、动态SQL修改、日志记录等功能。在这个主题中,我们将深入探讨如何利用MyBatis拦截器实现分页以及动态地...

    MyBatis拦截器:给参数对象属性赋值的实例

    在MyBatis中,拦截器通过实现`Interceptor`接口来创建。`OpeInfoInterceptor`类就是这样一个拦截器,它使用了`@Intercepts`注解来指定拦截的目标。`@Signature`注解用来精确定义拦截的方法,这里是`Executor`类的`...

    通过Mybatis拦截器自动定位慢SQL并记录日志

    总结来说,通过Mybatis的拦截器,我们可以方便地实现自动定位和记录慢SQL的功能,从而更好地管理和优化数据库查询性能。这个过程涉及到拦截器的创建、拦截方法的编写、配置文件的修改以及日志的分析和处理,是提升...

    MyBatis拦截器 添加查询条件动态修改sql

    通过mybatis的拦截器,实现为所有sql(或指定sql) 统一添加查询条件,譬如通过线程变量传递某参数(日期),来实现对指定参数的数据筛选,而不需要在每个查询前,手动将该条件注入到查询中。因该资料网络较少,故特此...

    mybatis使用拦截器实现分页操作

    在MyBatis中,我们可以通过实现`org.apache.ibatis.plugin.Interceptor`接口并重写`intercept`方法来创建一个自定义拦截器。 分页是数据库操作中常见的需求,传统的做法是在每个查询方法中添加分页逻辑,但这会导致...

    Mybatis拦截器记录数据更新历史记录到MongoDB

    在“Mybatis拦截器记录数据更新历史记录到MongoDB”这个项目中,我们需要创建一个自定义的拦截器类,该类需要实现`org.apache.ibatis.plugin.Interceptor`接口并覆写`intercept`方法。在这个方法里,我们可以捕获到...

    mybatis拦截器的完整实现

    在这个"mybatis拦截器的完整实现"项目中,我们可以通过设置拦截器来观察并控制SQL的执行过程。 首先,我们要理解MyBatis拦截器的工作原理。MyBatis使用了Java的动态代理和AOP(面向切面编程)思想,拦截器实际上是...

    mybatis拦截器修改执行sql语句

    通过mybatis拦截器将查询语句、更新语句、删除语句、插入语句中指定表明替换为另一个表名

    springboot+mybatis拦截器实现自动分页

    MyBatis本身并不直接支持分页,但可以通过拦截器或者自定义插件的方式来实现。这里我们将使用拦截器。 1. **创建拦截器**: 首先,我们需要创建一个实现了`Interceptor`接口的类。在`intercept`方法中,我们可以获取...

    MyBatis拦截器分页

    MyBatis拦截器分页是实现数据库查询优化和提高应用性能的一种有效手段。在MyBatis框架中,拦截器扮演着动态代理的角色,允许我们在执行SQL之前或之后进行额外的操作,比如统计、日志记录或者在本例中的分页处理。...

    MyBatis拦截器(csdn)————程序.pdf

    通过拦截器,可以实现诸如分页、插入和更新时间/人、数据权限、SQL监控日志等功能,从而提高开发效率。 MyBatis支持四种对象拦截:Executor、StatementHandler、ParameterHandler和ResultSetHandler。 Executor是...

    mybatis 分页拦截器及拦截器配置

    MyBatis 分页拦截器是实现数据库查询分页效果的一种高效解决方案。在传统的SQL查询中,我们通常需要手动编写分页语句,但这容易出错且不易维护。通过使用分页拦截器,我们可以将分页逻辑封装起来,使得在编写Mapper...

    MyBatis拦截器及分页插件

    MyBatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。Interceptor接口中定义了三个方法:intercept、plugin和setProperties。intercept方法是拦截器要执行的方法,plugin方法是拦截...

    Mybatis拦截器实现统计sql执行时间及打印完整sql语句

    Mybatis 拦截器是 Mybatis 框架中的一种高级特性,它允许开发者在特定的执行点插入自定义的代码逻辑。在本例中,我们关注的是如何使用拦截器来实现 SQL 执行时间的统计以及打印完整的 SQL 语句。这在调试和性能优化...

    Mybatis自定义拦截器,对模糊查询传值的特殊字符统一进行转义处理的代码

    特殊字符(\,_,%)转义工具类 MyQueryInterceptor.java: Mybatis自定义拦截器 注意:该拦截器只支持QueryWrapper的like方法,serviceImpl层传全角模糊查询(%%) mapper或xml层的全角模糊查询(%*%)和半角模糊查询(%*或*%)

    Mybatis自定义拦截器,对模糊查询传值的特殊字符(\,_,%)统一进行转义处理的代码

    代码包含: EscapeUtil.java:特殊字符(\,_,%)转义工具类 MyQueryInterceptor.java: Mybatis自定义拦截器 注意:该拦截器只支持QueryWrapper的like方法,serviceImpl层传全角模糊查询(%%) mapper或xml层的全角模糊查询(%...

Global site tag (gtag.js) - Google Analytics