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;
- }
- }
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的实现的更多相关文章
- node连续查询两次数据库返回方式(文档未定)
function db(callback){ var mysql = require('mysql'); var connection = mysql.createConnection({ host ...
- MyBatis 返回 Map 字段丢失问题
问题现象 执行存储过程返回 Map 集合数据,发现有字段丢失情况,仔细研究发现丢失的字段值都为 NULL. 解决办法1: 在查询 SQL 语句中增加 NULL 判断函数 MSSQL: isnull(字 ...
- spring boot整合mybatis查询数据库返回Map字段为空不返回解决
1.出现问题原因原因1:mybatis的配置即mapper返回映射配置. 原因2:jackson的配置即@ResponseBody序列化配置. 2.解决方式步骤1:解决原因1 mybatis: con ...
- mybatis返回map类型数据空值字段不显示(三种解决方法)
转http://blog.csdn.net/lulidaitian/article/details/70941769 一.查询sql添加每个字段的判断空 IFNULL(rate,'') as rate ...
- mybatis返回map类型数据空值字段不显示的解决方法
在日常开发中,查询数据返回类型为map,数据库中有些自动值为null,则返回的结果中没有值为空的字段,则如何显示值为空的字段呢? Spring boot + MyBatis返回map中null值默认不 ...
- 【mybatis】mybatis查询 结果 用map接收,无实体接收 + 关联子表 一并返回主子表的结果
如果后台程序没有实体对应mysql的数据表. 而mybatis想要查询mysql这个数据表的数据,返回给应用程序. 应用程序该如何接收? =============================== ...
- 使用MyBatis时接收值和返回值选择Map类型或者实体类型
MyBatis作为现近JavaEE企业级项目开发中常用的持久层框架之一,以其简洁高效的ORM映射和高度的SQL的自由性被广大开发人员认可.Mybatis在接收系统传来的参数和返回的参数时主要可以有Ma ...
- Mybatis,返回Map的时候,将Map内的Key转换为驼峰的命名
每次使用mybatis的时候,简单的连表查询,用Map接收的时候,都是像DB定义的字段一样,类似以下 student_name,student_id,没有转换为驼峰,但是又不能因为这一个定义一个jav ...
- 【问题记录】MyBatis查询数据库返回多个不同类型参数的结果集的接收方式
其实是个非常简单的问题,但是这玩意儿弄得我很难受,又浪费了一个下午的时间,简直了…… 问题大概是,我在查询数据库时,查询的结果有两个,一个是varchar格式的字段,一个int格式字段,例如: sel ...
随机推荐
- 站内信DB设计实现
两年前,万仓一黍在博客园发了两篇关于站内信的设计实现博文,<群发"站内信"的实现>.<群发"站内信"的实现(续)>,其中阐述了他关于站内 ...
- [网络] SOCKET, TCP/UDP, HTTP, FTP
(一)TCP/UDP,SOCKET,HTTP,FTP简析 TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层: 网络层:IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协议 传 ...
- IOS开发之Post 方式获取服务器数据
//1.创建post方式的 参数字符串url +(NSString *)createPostURL:(NSMutableDictionary *)params { NSString *postStri ...
- 定时关机命令——shutdown
通常会用到的定时关机命令有两种: Shutdown -s -t 36001小时后自己主动关机(3600秒) at 12:00 Shutdown -s 12:00自己主动关闭计算机 系统定时关机: Wi ...
- Android学习四、Android中的Adapter
一.Adapter的介绍 An Adapter object acts as a bridge between an AdapterView and the underlying data for t ...
- JS和利用openssl的object C加密得到相同的aes加密密文
这是之前接到的一个工作内容,项目原本的登录操作是获得账号和密码以后,对密码进行一遍MD5加密,然后传递账号和密文到cgi文件.在c中获取到账户以后,从数据库中获取到密码,对密码进行一次MD5的加密,然 ...
- java 对list中对象按属性排序
实体对象类 --略 排序类----实现Comparator接口,重写compare方法 package com.tang.list; import java.util.Comparator; publ ...
- 几种破解MySQL root密码的几种方法:
几种破解MySQL root密码的几种方法: 方法一 使用phpmyadmin,这是最简单的了,修改mysql库的user表,不过别忘了使用PASSWord函数. 方法二 使用mysqladmin,这 ...
- 武汉科技大学ACM:1010: 电话号码
Problem Description LXD打算换个手机号码,但是他去营业厅选号码的时候却把移动的客服小姐烦得不行,因为他太挑三捡四啦.对于一个手机号的后六位数字(前面五位他就无所谓了),LXD有很 ...
- Eclipse导入JavaWeb项目报错:The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path
JavaWeb项目中写的JSP页面需要Web容器解析处理成HTML才能展示到前端浏览器,解析JSP需要Web容器.JSP页面顶端出现“红色”的报错信息:The superclass "jav ...