Mybatis返回Map的一种实现

来源:互联网 发布:linux 将多个文件压缩 编辑:程序博客网 时间:2024/05/16 07:13
Mybatis返回Map的一种实现


博客分类: mybatis
mybatis返回map列key
Mybatis返回Map的一种实现
前言


       在使用Mybatis进行系统开发的时候,有时候我们会有这么一种需求:我们希望通过Mybatis查询某一个表返回的结果是一个Map,而这个Map的Key是表的一个字段,Value是另一个字段。然而当我们按照Mybatis的做法,指定查询Mapper语句的resultType为map时返回的结果是一个Map列表(表中有多条记录时),而且每个元素Map对应的是表的一行记录(Key为每个字段的名称,Value为对应的值),这跟我们的需求是不相符合的。那有什么方法可以让Mybatis查询出来的结果是一个Key为表中某一列的值,Value为表中另一列的值的Map呢?今天将跟大伙介绍一种使用Mybatis拦截器来实现查询返回Map的方法。关于Mybatis拦截器的介绍可以查看这篇博客。
实现


       返回结果是由ResultSetHandler的handleResultSets方法对当前的Statement处理后的返回结果,所以我们如果要改变返回结果的话就需要使用Mybatis的拦截器对ResultSetHandler接口的handleResultSets方法进行拦截。
我们不可能把所有的ResultSet都拦截,然后返回对应的Map,所以我们需要在拦截到handleResultSets方法之后进行筛选,对于不需要拦截的调用Invocation的proceed()方法,而需要拦截的则实现我们自己的逻辑,返回对应的结果。对于这种筛选条件一般都是通过ParameterObject来进行的。这里我自定义了一个类,叫MapParam,当某一个语句需要返回一个Map时,就指定其参数类型为MapParam。
这个参数类型MapParam一定需要有三个作用:
(1)    可以指定哪个字段为返回Map的Key;
(2)    可以指定哪个字段为返回Map的Value;
(3)    可以附带其他参数;
综合以上三点考虑,我觉得比较合适的MapParam应该是一个Map,当然这个Map是特用的,或者其包含一个用于存放参数的Map的引用。这里我选择使用第一种方式,定义一个继承自HashMap的类。 
Java代码  
public class MapParam extends HashMap<String, Object> {  
  
    /** 
     *  
     */  
    private static final long serialVersionUID = 1L;  
  
    /** 
     * 作为Key的字段对应MapParam的Key 
     */  
    public static final String  KEY_FIELD = "mapKeyField";  
    /** 
     * 作为Value的字段对应MapParam的Key 
     */  
    public static final String VALUE_FIELD = "mapValueField";  
      
    public MapParam() {  
          
    }  
      
    /** 
     * 指定keyField和valueField 
     * @param keyField Map中key对应的字段 
     * @param valueField Map中value对应的字段 
     */  
    public MapParam(String keyField, String valueField) {  
        this.put(KEY_FIELD, keyField);  
        this.put(VALUE_FIELD, valueField);  
    }  
      
}  
  
       从上面定义的这个参数类我们可以看到,我们定义了使用哪个字段作为返回结果的Key,哪个字段作为返回结果的Value,同时我们还可以往里面put其他参数供SQL使用。
       接着就是来实现我们的拦截器了。这里我们定义一个MapInterceptor用于拦截对应的结果集返回一个Map。其代码如下所示:
Java代码  
@Intercepts(@Signature(method="handleResultSets", type=ResultSetHandler.class, args={Statement.class}))  
public class MapInterceptor implements Interceptor {  
  
    /* (non-Javadoc) 
     * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation) 
     */  
    public Object intercept(Invocation invocation) throws Throwable {  
        //通过invocation获取代理的目标对象  
        Object target = invocation.getTarget();  
        //暂时ResultSetHandler只有FastResultSetHandler这一种实现  
        if (target instanceof FastResultSetHandler) {  
            FastResultSetHandler resultSetHandler = (FastResultSetHandler) target;  
            //利用反射获取到FastResultSetHandler的ParameterHandler属性,从而获取到ParameterObject;  
            ParameterHandler parameterHandler = ReflectUtil.getFieldValue(resultSetHandler, "parameterHandler");  
            Object parameterObj = parameterHandler.getParameterObject();  
            //判断ParameterObj是否是我们定义的MapParam,如果是则进行自己的处理逻辑  
            if (parameterObj instanceof MapParam) {//拦截到了  
                MapParam mapParam = (MapParam) parameterObj;  
                //获取到当前的Statement  
                Statement stmt = (Statement) invocation.getArgs()[0];  
                //通过Statement获取到当前的结果集,对其进行处理,并返回对应的处理结果  
                return handleResultSet(stmt.getResultSet(), mapParam);  
            }  
        }  
        //如果没有进行拦截处理,则执行默认逻辑  
        return invocation.proceed();  
    }  
  
    /** 
     * 处理结果集 
     * @param resultSet 
     * @param mapParam 
     * @return 
     */  
    private Object handleResultSet(ResultSet resultSet, MapParam mapParam) {  
        // TODO Auto-generated method stub  
        if (resultSet != null) {  
            //拿到Key对应的字段  
            String keyField = (String) mapParam.get(MapParam.KEY_FIELD);  
            //拿到Value对应的字段  
            String valueField = (String) mapParam.get(MapParam.VALUE_FIELD);  
            //定义用于存放Key-Value的Map  
            Map<Object, Object> map = new HashMap<Object, Object>();  
            //handleResultSets的结果一定是一个List,当我们的对应的Mapper接口定义的是返回一个单一的元素,并且handleResultSets返回的列表  
            //的size为1时,Mybatis会取返回的第一个元素作为对应Mapper接口方法的返回值。  
            List<Object> resultList = new ArrayList<Object>();  
            try {  
                //把每一行对应的Key和Value存放到Map中  
                while (resultSet.next()) {  
                    Object key = resultSet.getObject(keyField);  
                    Object value = resultSet.getObject(valueField);  
                    map.put(key, value);  
                }  
            } catch (SQLException e) {  
                e.printStackTrace();  
            } finally {  
                closeResultSet(resultSet);  
            }  
            //把封装好的Map存放到List中并进行返回  
            resultList.add(map);  
            return resultList;  
        }  
        return null;  
    }  
  
    /** 
     * 关闭ResultSet 
     * @param resultSet 需要关闭的ResultSet 
     */  
    private void closeResultSet(ResultSet resultSet) {  
        try {  
            if (resultSet != null) {  
                resultSet.close();  
            }  
        } catch (SQLException e) {  
              
        }  
    }  
  
    /* (non-Javadoc) 
     * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object) 
     */  
    public Object plugin(Object obj) {  
        return Plugin.wrap(obj, this);  
    }  
  
    /* (non-Javadoc) 
     * @see org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties) 
     */  
    public void setProperties(Properties props) {  
          
    }  
  
}  
 
       拦截器的实现这一块就没什么好讲的了,就是简单的把Statement对应的结果集封装成一个Map,再把用于返回的Map封装成一个List进行返回。里面的代码很简单,相信大伙都看得懂。而对于Mybatis拦截器不懂的可以看这篇文章。
       在上面拦截器的实现中用到了一个工具类ReflectUtil,其代码如下所示:
 
Java代码  
public class ReflectUtil {  
    /** 
     * 利用反射获取指定对象的指定属性 
     * @param obj 目标对象 
     * @param fieldName 目标属性 
     * @return 目标属性的值 
     */  
    @SuppressWarnings("unchecked")  
    public static <T> T 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 (T)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拦截器实现Mybatis返回Map的方法就介绍的差不多了,接下来我们举一个例子来说一下它的应用。
应用


       首先,我们需要把它配置起来,让Mybatis能够对它进行注册。配置的方法很简单,就是在Mybatis的配置文件中利用plugins元素下面的plugin进行注册。
 
Xml代码  
<plugins>  
   <plugin interceptor="com.tiantian.mybatis.interceptor.MapInterceptor"/>  
</plugins>  
 
       而对于使用Spring整合Mybatis的用户来说,如果你在整合的时候是把Mybatis的配置都放在Mybatis的配置文件中,然后通过Spring配置文件中的bean SqlSessionFactoryBean的configLocation来指定这个配置文件的位置的话你的配置依然可以像上面那样写在Mybatis的配置文件中;但如果你是通过SqlSessionFactoryBean的属性来指定Mybatis配置文件中的一些内容的话,那么你的拦截器应该是通过SqlSessionFactoryBean的plugins属性来指定的,你的配置文件看起来大概是这样子:
 
Xml代码  
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
   ……  
   <property name="plugins">  
       <array>  
          <bean class="com.tiantian.mybatis.interceptor.MapInterceptor"/>  
       </array>  
   </property>  
    ……  
</bean>  
 
       配置好以后我们就可以使用该拦截器进行相应的拦截了。
       这里假设我们有一个表t_city,该表一共有三个字段,id、code和name。现在我们希望通过Mybatis来查询该表时返回结果是一个Map,其中key为每行字段code的值,value为对应字段name的值。这个时候我们的Mapper语句大概是这样:
Xml代码  
<select id="find" resultType="map" parameterType="MapParam">  
   select code, name from t_city  
</select>  
 
       其对应的Mapper接口方法大概是这样:
 
Java代码  
public interface CityMapper extends SuperMapper {  
   
    public Map<Object, Object> find(MapParam param);  
      
}  
 
       那么,当我们去调用的时候大概是这样调用的,准备好一个参数对象MapParam,赋好对应的属性之后把它传递给对应的Mapper接口方法:
 
Java代码  
@Test  
public void testMap() {  
   MapParam param = new MapParam("code", "name");  
   Map<Object, Object> result = cityMapper.find(param);  
   System.out.println(result);  
}  
 
说明


       在这篇博客中,我只是介绍使用Mybatis拦截器实现Mybatis查询返回Map的这么一种方法,里面的代码考虑是比较粗糙的,想要在使用Mybatis拦截器拦截到ResultSetHandler的handleResultSets方法,对ResultSet做更加严谨的处理的话,推荐参考Mybatis ResultSetHandler接口的实现类FastResultSetHandler关于handleResultSets(Statement stmt)的实现。
0 0
原创粉丝点击