zoukankan      html  css  js  c++  java
  • MyBatis查询两个字段,返回Map,一个字段作为key,一个字段作为value的实现

    1. 问题描述

      在使用MyBatis,我们经常会遇到这种情况:SELECT两个字段,需要返回一个Map,其中第一个字段作为key,第二个字段作为value。MyBatis的MapKey虽然很实用,但并不能解决这种场景。这里,就介绍一种使用拦截器来解决这个问题的方案。

    2. 解决方案

    源码详见:spring-mybatis-test

    2.1 注解

    package com.adu.spring_test.mybatis.annotations;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 将查询结果映射成map的注解,其中第一个字段为key,第二个字段为value.
     * <p>
     * 注:返回类型必须为{@link java.util.Map Map<K, V>}。K/V的类型通过MyBatis的TypeHander进行类型转换,如有必要可自定义TypeHander。
     *
     * @author yunjie.du
     * @date 2016/12/22 18:44
     */
    @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;
    }

    2.2 拦截器

    package com.adu.spring_test.mybatis.interceptor;
    
    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.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Properties;
    
    import org.apache.commons.lang3.StringUtils;
    import org.apache.ibatis.executor.resultset.ResultSetHandler;
    import org.apache.ibatis.mapping.MappedStatement;
    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;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.type.TypeHandler;
    import org.apache.ibatis.type.TypeHandlerRegistry;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.dao.DuplicateKeyException;
    
    import com.adu.spring_test.mybatis.annotations.MapF2F;
    import com.adu.spring_test.mybatis.util.ReflectUtil;
    
    import javafx.util.Pair;
    
    /**
     * MapF2F的拦截器
     * 
     * @author yunjie.du
     * @date 2016/12/22 18:44
     */
    @Intercepts(@Signature(method = "handleResultSets", type = ResultSetHandler.class, args = { Statement.class }))
    public class MapF2FInterceptor implements Interceptor {
        private Logger logger = LoggerFactory.getLogger(MapF2FInterceptor.class);
    
        @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 currentMethod = findMethod(className, currentMethodName);// 获取当前Method
    
            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];
            Pair<Class<?>, Class<?>> kvTypePair = getKVTypeOfReturnMap(currentMethod);// 获取返回Map里key-value的类型
            TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();// 获取各种TypeHander的注册器
            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);
                    }
                }
    
                map.put(key, value);// 第一列作为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);
    
        }
    
    }

    2.3 ReflectUtil

    package com.adu.spring_test.mybatis.util;
    
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.reflection.SystemMetaObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 反射工具类
     */
    public class ReflectUtil {
        private static final Logger logger = LoggerFactory.getLogger(ReflectUtil.class);
    
        /**
         * 分离最后一个代理的目标对象
         * 
         * @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;
        }
    
    }
    View Code

    2.4 MyBatis Datasource配置拦截器

        <!-- session factory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="configLocation" value="classpath:mybatis/mybatis-data-config.xml" />
            <property name="mapperLocations" value="classpath:mapper/**/*.xml" />
            <property name="plugins">
                <array>
                    <bean class="com.adu.spring_test.mybatis.interceptor.MapF2FInterceptor"/>
                </array>
            </property>
        </bean>

    2.5 简例

        /**
         * 批量获取用户姓名
         * 
         * @param ids
         * @return key为ID,value为username
         */
        @MapF2F()
        Map<Long, String> queryUserNamesByIds(@Param("ids") List<Long> ids);
    

      

    <select id="queryUserNamesByIds" resultType="map">
        SELECT id, user_name
        FROM user_info
        WHERE id IN
            <foreach collection="ids" open="(" close=")" separator="," item="item">
                #{item}
            </foreach>
    </select>

    参考:

     

  • 相关阅读:
    让treeview控件的滚动条移动到顶部的位置
    用javascript 来将word 转成 html 更加简单,三行代码搞定
    在asp.net中,添加itemtempert 项模板时,如果在项模板里有其它控件,如何控件这些控件的属性?
    C#格式化数值结果表 数字的格式化输出
    直接创建一个XmlDocument文档
    实现 网页的 数据加载中.... 效果,很简单哦
    c#编写XML读写删改功能,算是比较全面的介绍XML操作的文章了。
    如何获取 电脑 的一些硬件信息。用于软件的加密等算法。
    关于正则表达式的使用一例。在Textbox 对话框内限制只能输入数字,如果输入出错,则清空内容。
    WPF中UI及时更新,如何在处理长时间工作时,保持界面的持续更新
  • 原文地址:https://www.cnblogs.com/waterystone/p/6214322.html
Copyright © 2011-2022 走看看