經過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實現一些須要關聯或者多表聯合查詢,統計呀等類型查詢方式的 一種解決方案。
相關文章
相關標籤/搜索