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;
} }

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>

参考:

 

MyBatis查询两个字段,返回Map,一个字段作为key,一个字段作为value的实现的更多相关文章

  1. node连续查询两次数据库返回方式(文档未定)

    function db(callback){ var mysql = require('mysql'); var connection = mysql.createConnection({ host ...

  2. MyBatis 返回 Map 字段丢失问题

    问题现象 执行存储过程返回 Map 集合数据,发现有字段丢失情况,仔细研究发现丢失的字段值都为 NULL. 解决办法1: 在查询 SQL 语句中增加 NULL 判断函数 MSSQL: isnull(字 ...

  3. spring boot整合mybatis查询数据库返回Map字段为空不返回解决

    1.出现问题原因原因1:mybatis的配置即mapper返回映射配置. 原因2:jackson的配置即@ResponseBody序列化配置. 2.解决方式步骤1:解决原因1 mybatis: con ...

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

    转http://blog.csdn.net/lulidaitian/article/details/70941769 一.查询sql添加每个字段的判断空 IFNULL(rate,'') as rate ...

  5. mybatis返回map类型数据空值字段不显示的解决方法

    在日常开发中,查询数据返回类型为map,数据库中有些自动值为null,则返回的结果中没有值为空的字段,则如何显示值为空的字段呢? Spring boot + MyBatis返回map中null值默认不 ...

  6. 【mybatis】mybatis查询 结果 用map接收,无实体接收 + 关联子表 一并返回主子表的结果

    如果后台程序没有实体对应mysql的数据表. 而mybatis想要查询mysql这个数据表的数据,返回给应用程序. 应用程序该如何接收? =============================== ...

  7. 使用MyBatis时接收值和返回值选择Map类型或者实体类型

    MyBatis作为现近JavaEE企业级项目开发中常用的持久层框架之一,以其简洁高效的ORM映射和高度的SQL的自由性被广大开发人员认可.Mybatis在接收系统传来的参数和返回的参数时主要可以有Ma ...

  8. Mybatis,返回Map的时候,将Map内的Key转换为驼峰的命名

    每次使用mybatis的时候,简单的连表查询,用Map接收的时候,都是像DB定义的字段一样,类似以下 student_name,student_id,没有转换为驼峰,但是又不能因为这一个定义一个jav ...

  9. 【问题记录】MyBatis查询数据库返回多个不同类型参数的结果集的接收方式

    其实是个非常简单的问题,但是这玩意儿弄得我很难受,又浪费了一个下午的时间,简直了…… 问题大概是,我在查询数据库时,查询的结果有两个,一个是varchar格式的字段,一个int格式字段,例如: sel ...

随机推荐

  1. js中的SetTimeOut

    1. SetTimeOut()              1.1 SetTimeOut()语法例子              1.2 用SetTimeOut()执行Function           ...

  2. 数据迁移sql

    1.把数据库test中的表Table1中的数据插入到数据库test2中的表Table2:insert into test2.Table2(a,c,d) select a,c,5 from test.T ...

  3. [置顶] Hash查找,散列查找

    //Hash.h #ifndef HASH_H #define HASH_H #define HASH_ARR_SIZE 100 #define FILL -1 #include <stdlib ...

  4. JStorm 是一个分布式实时计算引擎

    alibaba/jstorm JStorm 是一个分布式实时计算引擎. JStorm 是一个类似Hadoop MapReduce的系统, 用户按照指定的接口实现一个任务,然后将这个任务递交给JStor ...

  5. Android Studio Gradle 缓存目录设置

    ======================================================== 笔者:qiujuer 博客:blog.csdn.net/qiujuer 站点:www. ...

  6. Unix C++(boost) 线程同步和线程组

    #include <boost/thread.hpp> #include <iostream> #include <vector> #include <cst ...

  7. webpy:页面下载的三种实现方式

    python: 1.import urllib urlretrieve() 方法直接将远程数据下载到本地. >>> help(urllib.urlretrieve)Help on f ...

  8. 常用html元素的取值和赋值方法总结

    1.获得type类型为TEXT或者AREATEXT的值 var textval = $("#text_id").attr("value"); var textv ...

  9. 软键盘 输入法管理器 InputMethodManager

     基本介绍 软键盘的显示原理 软键盘其实是一个Dialog.InputMethodService为我们的输入法创建了一个Dialog,并且对某些参数进行了设置,使之能够在底部或者全屏显示.当我们点击输 ...

  10. ASP.NET-FineUI开发实践-3

    1.参照模拟数据库分页通过缓存重写内存分页,优化页面响应速度 Grid的响应速度是硬伤,我写了个通用方法把所有数据放在缓存中模拟数据库分页,比自带的缓存分页快很多,这里贴上实体类的通用方法,DataT ...