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);@Overridepublic 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);// 获取当前Methodif (currentMethod == null || currentMethod.getAnnotation(MapF2F.class) == null) {// 如果当前Method没有注解MapF2Freturn 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);}@Overridepublic Object plugin(Object obj) {return Plugin.wrap(obj, this);}@Overridepublic 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_nameFROM user_infoWHERE id IN<foreach collection="ids" open="(" close=")" separator="," item="item">#{item}</foreach>
</select>

参考:

  • Mybatis返回Map的一种实现

 

转载于:https://www.cnblogs.com/waterystone/p/6214322.html

MyBatis查询两个字段,返回Map,一个字段作为key,一个字段作为value的实现相关推荐

  1. 老司机学习MyBatis之如何通过select返回Map

    From: https://blog.csdn.net/Gaomb_1990/article/details/80638177 一.案例 当要查询的结果是一个Map的时候,这里分为两种情况: ①返回单 ...

  2. mybatis支持驼峰自动转换sql吗_mybatis-plus返回map自动转驼峰配置操作

    mybatis-plus返回map自动转驼峰配置object-wrapper-factory不生效问题解决:配置map-underscore-to-camel-case: true不生效问题解决 很多 ...

  3. MySqlClient访问tinyint字段返回布尔值

    MySqlClient访问tinyint字段返回布尔值 原文 MySqlClient访问tinyint字段返回布尔值 症状: 使用MySqlClient访问tinyint       unsign 字 ...

  4. MySQL.MyBatis怎么将查询的两个字段作为Map的key和value

    MySQL.MyBatis怎么将查询的两个字段作为Map的key和value 问题的由来 前端使用Echarts图标显示汇总数据.需要形式如下的数据: {"C20": 42.01, ...

  5. 【开源项目笔记:platform-wechat-mall】Mybatis 查询数据库返回部分字段

    在本开源项目中数据库操作采用了Mybatis,不美的是查询数据一律返回VO结构,即使只需要其中的一两个字段也如此,更别提多表联合查询时空字段占了90%,需要改进. 本文描述Mybatis如何在单表查询 ...

  6. mybatis查询返回map的问题

    文章目录 背景 1.mybatis只返回单个map 2.查询返回map的list 3.利用mybatis的@MapKey注解返回map 4.重写handler 背景 假设背景: 想获取某个省下各个市有 ...

  7. mybatis查询返回null的原因_可怕!你没看错,这次确实是纯手工实现一个MyBatis框架...

    目录 前言 JDBC MyBatis 源码分析 前置知识 原理分析 自己实现一个 MyBatis 框架 前言 MyBatis是一个非常优秀的持久层应用框架,目前几乎已经一统天下.既然是持久层框架,那么 ...

  8. map mybatis 的字段返回0_mybatis返回map类型数据空值字段不显示(三种解决方法)

    mybatis的配置 mybatis-config.xml 2,springBoot配置   application.properties 添加 #mybatis resultType equal m ...

  9. mybatis 字段名自动转小写_mybatis 返回Map类型key改为小写的操作

    默认情况下,当resultType="java.util.Map"时,返回的key值都是大写的. 现在想key改成自己想要的,只需为查询出来的字段增加个别名即可. 如: selec ...

最新文章

  1. Oracle PL/SQL的安装
  2. 3行代码,Python数据预处理提速6倍!(附链接)
  3. 带负荷测试要求二次最小电流_检修状态下二次带负荷测试方案的优化研究
  4. 数据库优化的几条基本策略
  5. 【全真互联网下音视频通信技术演进】
  6. 我说省略号然后点点点点点点
  7. 如何下载HLS视频到本地(m3u8)
  8. 涤纶针织物用分散染料染色时,为什么小样与大样不符?
  9. Spring velocity 中文乱码 解决方案
  10. 无源蜂鸣器c语言编程,无源蜂鸣器鸣叫
  11. 非线性回归(Non-linear Regression)学习笔记
  12. ORB_SLAM3系统框图
  13. 4.微信支付之刷卡支付
  14. 什么是ICP经营许可证?
  15. win7 win10双系统开机系统引导
  16. JAVA写100以内的偶数和
  17. jQJQJQJQJQJQ
  18. zigbee3.0 BDB 介绍(一)
  19. OpenCv中Numpy常用函数
  20. javascript 编码_我们的1,600小时JavaScript编码课程

热门文章

  1. jQuery 实现 select模糊查询 反射机制
  2. 如何将CSDN文档转换成IPYNB格式的文档?
  3. 全国大学生智能汽车竞赛 --智慧物流创意组
  4. 第十五届全国大学生智能车全国总决赛获奖信息-浙江赛区
  5. LED,硅光电池的光能-电能转换是可逆的吗?
  6. AD7705 16-bit Delta-Sigma AD 转换器
  7. 数字示波器使用中的欠采样
  8. int的长度_Java中String长度有限制吗?身边的同事每一个人知道!
  9. applicationcontext添加配置_Spring源码分析2 — spring XML配置文件的解析流程
  10. php怎样弄成中文,php怎样替换中文字符