通过注解实现MyBatis将sql查询结果的两个字段分别作为map的key,value

在使用mybatis时,遇到一个这样一个场景:select语句,返回两个字段,这两个字段分别作为key和value返回一个Map,返回一个Map<String,Object>。
 
可以通过重写ResultHandler的方式实现这个功能,感觉不是很灵活。
 
以下通过注解实现,相对来说非常灵活,给需要映射may<key,value>的dao层接口方法加上自定义的注解即可实现。
 
首先定义注解
package com.lsz.config.enums;


import java.lang.annotation.*;

/**
* 将查询结果映射成map的注解,其中第一个字段为key,第二个字段为value.
* 注:返回类型必须为{@link java.util.Map Map<K, V>}。K/V的类型通过MyBatis的TypeHander进行类型转换,如有必要可自定义TypeHander。
*
* @author lishuzhen
* @date 2020/11/2 13:40
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MapF2F {
    /**
     * 是否允许key重复。如果不允许,而实际结果出现了重复,会抛出org.springframework.dao.DuplicateKeyException。
     *
     * @return
     */
    boolean isAllowKeyRepeat() default true;


    /**
     * 对于相同的key,是否允许value不同(在允许key重复的前提下)。如果允许,则按查询结果,后面的覆盖前面的;如果不允许,则会抛出org.springframework.dao.DuplicateKeyException。
     *
     * @return
     */
    boolean isAllowValueDifferentWithSameKey() default false;
}

定义一个拦截器

package com.lsz.config.interceptor;


import com.lsz.config.enums.MapF2F;
import com.lsz.config.utils.ReflectUtil;
import javafx.util.Pair;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.dao.DuplicateKeyException;


import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;


/**
* @author lishuzhen
* @date 2020/11/2 15:36
*/
@Intercepts(@Signature(method = "handleResultSets", type = ResultSetHandler.class, args = {Statement.class}))
public class MapF2FInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MetaObject metaStatementHandler = ReflectUtil.getRealTarget(invocation);
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("mappedStatement");
        // 当前类
        String className = StringUtils.substringBeforeLast(mappedStatement.getId(), ".");
        // 当前方法
        String currentMethodName = StringUtils.substringAfterLast(mappedStatement.getId(), ".");
        // 获取当前Method
        Method currentMethod = findMethod(className, currentMethodName);


        if (currentMethod == null || currentMethod.getAnnotation(MapF2F.class) == null) {
            // 如果当前Method没有注解MapF2F
            return invocation.proceed();
        }


        // 如果有MapF2F注解,则这里对结果进行拦截并转换
        MapF2F mapF2FAnnotation = currentMethod.getAnnotation(MapF2F.class);
        Statement statement = (Statement) invocation.getArgs()[0];
        // 获取返回Map里key-value的类型
        Pair<Class<?>, Class<?>> kvTypePair = getKVTypeOfReturnMap(currentMethod);
        // 获取各种TypeHander的注册器
        TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        return result2Map(statement, typeHandlerRegistry, kvTypePair, mapF2FAnnotation);


    }


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


    @Override
    public void setProperties(Properties properties) {


    }


    /**
     * 找到与指定函数名匹配的Method。
     *
     * @param className
     * @param targetMethodName
     * @return
     * @throws Throwable
     */
    private Method findMethod(String className, String targetMethodName) throws Throwable {
        // 该类所有声明的方法
        Method[] methods = Class.forName(className).getDeclaredMethods();
        if (methods == null) {
            return null;
        }


        for (Method method : methods) {
            if (StringUtils.equals(method.getName(), targetMethodName)) {
                return method;
            }
        }


        return null;
    }


    /**
     * 获取函数返回Map中key-value的类型
     *
     * @param mapF2FMethod
     * @return left为key的类型,right为value的类型
     */
    private Pair<Class<?>, Class<?>> getKVTypeOfReturnMap(Method mapF2FMethod) {
        Type returnType = mapF2FMethod.getGenericReturnType();


        if (returnType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) returnType;
            if (!Map.class.equals(parameterizedType.getRawType())) {
                throw new RuntimeException(
                        "[ERROR-MapF2F-return-map-type]使用MapF2F,返回类型必须是java.util.Map类型!!!method=" + mapF2FMethod);
            }


            return new Pair<>((Class<?>) parameterizedType.getActualTypeArguments()[0],
                    (Class<?>) parameterizedType.getActualTypeArguments()[1]);
        }


        return new Pair<>(null, null);
    }


    /**
     * 将查询结果映射成Map,其中第一个字段作为key,第二个字段作为value.
     *
     * @param statement
     * @param typeHandlerRegistry MyBatis里typeHandler的注册器,方便转换成用户指定的结果类型
     * @param kvTypePair          函数指定返回Map key-value的类型
     * @param mapF2FAnnotation
     * @return
     * @throws Throwable
     */
    private Object result2Map(Statement statement, TypeHandlerRegistry typeHandlerRegistry,
                              Pair<Class<?>, Class<?>> kvTypePair, MapF2F mapF2FAnnotation) throws Throwable {
        ResultSet resultSet = statement.getResultSet();
        List<Object> res = new ArrayList();
        Map<Object, Object> map = new HashMap();


        while (resultSet.next()) {
            Object key = this.getObject(resultSet, 1, typeHandlerRegistry, kvTypePair.getKey());
            Object value = this.getObject(resultSet, 2, typeHandlerRegistry, kvTypePair.getValue());


            if (map.containsKey(key)) {// 该key已存在
                if (!mapF2FAnnotation.isAllowKeyRepeat()) {
                    // 判断是否允许key重复
                    throw new DuplicateKeyException("MapF2F duplicated key!key=" + key);
                }


                Object preValue = map.get(key);
                if (!mapF2FAnnotation.isAllowValueDifferentWithSameKey() && !Objects.equals(value, preValue)) {
                    // 判断是否允许value不同
                    throw new DuplicateKeyException("MapF2F different value with same key!key=" + key + ",value1="
                            + preValue + ",value2=" + value);
                }
            }
            // 第一列作为key,第二列作为value。
            map.put(key, value);
        }


        res.add(map);
        return res;
    }


    /**
     * 结果类型转换。
     * <p>
     * 这里借用注册在MyBatis的typeHander(包括自定义的),方便进行类型转换。
     *
     * @param resultSet
     * @param columnIndex         字段下标,从1开始
     * @param typeHandlerRegistry MyBatis里typeHandler的注册器,方便转换成用户指定的结果类型
     * @param javaType            要转换的Java类型
     * @return
     * @throws SQLException
     */
    private Object getObject(ResultSet resultSet, int columnIndex, TypeHandlerRegistry typeHandlerRegistry,
                             Class<?> javaType) throws SQLException {
        final TypeHandler<?> typeHandler = typeHandlerRegistry.hasTypeHandler(javaType)
                ? typeHandlerRegistry.getTypeHandler(javaType) : typeHandlerRegistry.getUnknownTypeHandler();


        return typeHandler.getResult(resultSet, columnIndex);


    }


}

定义一个处理反射的工具类

 

package com.lsz.config.utils;


import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;


import org.apache.ibatis.plugin.Invocation;


/**
* @author lishuzhen
* @date 2020/11/2 15:37
*/
public class ReflectUtil {
    /**
     * 分离最后一个代理的目标对象
     *
     * @param invocation
     * @return
     */
    public static MetaObject getRealTarget(Invocation invocation) {
        MetaObject metaStatementHandler = SystemMetaObject.forObject(invocation.getTarget());


        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }


        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }


        return metaStatementHandler;
    }
}

在mybatis配置sqlSessionFactory中加入拦截器

<!-- MyBatis 配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="DataSource" />
   <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
   <!-- 需要映射的包路径 -->
   <property name="typeAliasesPackage" value="com.lsz.**" />
   <property name="mapperLocations">
      <list>
         <value>classpath:com/lsz/**/*Mapper.xml</value>
      </list>
   </property>
   <property name="plugins">
      <array>
         <bean class="com.lsz.config.interceptor.MapF2FInterceptor"/>
      </array>
   </property>
</bean>

在使用时,直接在dao的接口层加入注解即可

/**
* 根据beanname和paramkey查询map参数
*
* @param query
* @return
*/
@MapF2F()
Map<String, String> selectByBeanNoAndParamKey(CfgMap query);
<select id="selectByBeanNoAndParamKey" parameterType="com.lsz.config.bean.CfgMap" resultType="java.util.Map">
    select map_key,map_value from cfg_map
    where bean_no = #{beanNo}
</select>
 
调用方法,打断点看结果
up-e58000aae612426f5428e5c9833730b5bed.png
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值