最近兩天項目需求研究了一下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實現一些須要關聯或者多表聯合查詢,統計呀等類型查詢方式的 一種解決方案。