Mybatis将多行结果以Key-Value形式填充进Map拦截器类

使用方式

  • 在Dao/Mapper接口的方法上添加注解@MapKeyV ,方法返回值设置为Map即可
  • xxxMapper.xml书写如下:
<select id="getMap" resultType="java.util.Map">
	SELECT id     AS `key`,
		   `name` AS `value`
	FROM `user`
	ORDER BY id
</select>

拦截器类

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.result.ResultMapException;
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.Signature;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

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

/**
 * Mybatis根据指定注解将查询结果集以KEY-VALUE形式填充到Map集合中
 */
@Intercepts(@Signature(method = "handleResultSets", type = ResultSetHandler.class, args = {Statement.class}))
public class MapKeyVInterceptor implements Interceptor {
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		MappedStatement mappedStatement;
		Object target = getOriginalObj(invocation);
		try {
			Field field = target.getClass().getDeclaredField("mappedStatement");
			field.setAccessible(true);
			Object obj = field.get(target);
			if (obj instanceof MappedStatement) mappedStatement = (MappedStatement) obj;
			else throw new NoSuchFieldException();
		} catch (NoSuchFieldException e) {
			throw new NoSuchFieldException("Try to get the real target object DefaultResultSetHandler and it's mappedStatement field, "
												   + "but the actual target object is " + target.getClass().getName());
		}
		
		// 获取当前执行SQL语句的Method对象
		Method method = findMethod(StringUtils.substringBeforeLast(mappedStatement.getId(), "."),
				StringUtils.substringAfterLast(mappedStatement.getId(), "."));
		
		// 如果当前Method没有注解MapKeyV
		if (method == null || method.getAnnotation(MapKeyV.class) == null) {
			return invocation.proceed();
		}
		
		// 如果有MapKeyV注解,则这里对结果进行拦截并转换
		MapKeyV mapKeyV = method.getAnnotation(MapKeyV.class);
		Statement statement = (Statement) invocation.getArgs()[0];
		// 获取返回Map里key-value的类型
		Pair<Class<?>, Class<?>> pair = getGenericReturnType(method);
		// 获取各种TypeHandler的注册器
		TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
		return fillResult2Map(statement, typeHandlerRegistry, pair, mapKeyV);
	}
	
	/**
	 * 根据{@link Invocation}的代理对象获取原始的被代理的目标对象
	 */
	public Object getOriginalObj(Invocation invocation) {
		Object obj = invocation.getTarget();
		// 如果当前对象是代理对象则循环查找上一级目标对象,直至目标对象不是代理对象时即为真实目标对象
		while (Proxy.isProxyClass(obj.getClass())) {
			try {
				Field field = obj.getClass().getSuperclass().getDeclaredField("h");
				field.setAccessible(true);
				Object plugin = field.get(obj);
				field = plugin.getClass().getDeclaredField("target");
				field.setAccessible(true);
				obj = field.get(plugin);
			} catch (NoSuchFieldException | IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}
		return obj;
	}
	
	/**
	 * 获取与指定类中指定方法名匹配的{@link Method}对象
	 * @param clazz  指定类名
	 * @param method 指定方法名称
	 */
	private Method findMethod(String clazz, String method) throws Throwable {
		if (StringUtils.isEmpty(clazz) || StringUtils.isEmpty(method))
			throw new IllegalArgumentException("The specified class name and method name cannot be empty");
		// 该类所有声明的方法
		Method[] methods = Class.forName(clazz).getDeclaredMethods();
		for (Method m : methods) {
			if (StringUtils.equals(m.getName(), method)) return m;
		}
		return null;
	}
	
	/**
	 * 获取当前方法返回的{@link Map}集合的泛型类型
	 * @param method 当前方法
	 */
	private Pair<Class<?>, Class<?>> getGenericReturnType(Method method) {
		Type returnType = method.getGenericReturnType();
		if (returnType instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) returnType;
			if (!Map.class.equals(parameterizedType.getRawType()))
				throw new RuntimeException("A method annotated with @MapKeyV must have a return type of Map");
			return new Pair<>(
					(Class<?>) parameterizedType.getActualTypeArguments()[0],
					(Class<?>) parameterizedType.getActualTypeArguments()[1]
			);
		}
		return new Pair<>(null, null);
	}
	
	/**
	 * 从结果集中选中与{@link MapKeyV}注解指定的 key、val 匹配的列放入到{@link Map}中
	 * @param statement           获取结果集的来源
	 * @param typeHandlerRegistry 类型转换注册器
	 * @param pair                返回的{@link Map}中的泛型类型
	 * @param mapKeyV             含有结果集映射别名以及其他参数
	 */
	private Object fillResult2Map(Statement statement, TypeHandlerRegistry typeHandlerRegistry,
								  Pair<Class<?>, Class<?>> pair, MapKeyV mapKeyV) throws Throwable {
		// 获取原始结果集
		ResultSet rs = getFirstResultSet(statement);
		
		// 如果结果集为null
		if (rs == null) return Collections.emptyMap();
		
		Map<Object, Object> result = new HashMap<>();
		
		while (rs.next()) {
			Object key, value;
			try {
				key = getObject(rs, mapKeyV.key(), typeHandlerRegistry, pair.getKey());
				value = getObject(rs, mapKeyV.val(), typeHandlerRegistry, pair.getValue());
			} catch (ResultMapException e) {
				throw new ResultMapException("Column name '" + mapKeyV.key() + "' or '" + mapKeyV.val() + "' does not exist!");
			}
			// 若key已存在,则根据注解参数判断是抛出否异常
			if (result.containsKey(key)) {
				if (!mapKeyV.allowKeyRepeat()) {
					// 判断是否允许key重复
					throw new IllegalArgumentException("The annotation @MapKeyV does not allow duplicate keys! key= " + key);
				}
				Object old = result.get(key);
				if (!mapKeyV.allowValueRepeat() && !Objects.equals(value, old)) {
					// 判断是否允许value不同
					throw new IllegalArgumentException(
							"In the case of the same key, the annotation @MapKeyV does not allow different values! key="
									+ key + ",values={ old: " + old + ",new: " + value);
				}
			}
			// 第一列作为key,第二列作为value。
			result.put(key, value);
		}
		return new ArrayList<Map<Object, Object>>() {{
			add(result);
		}};
	}
	
	private ResultSet getFirstResultSet(Statement stmt) throws SQLException {
		ResultSet rs = stmt.getResultSet();
		while (rs == null) {
			if (stmt.getMoreResults()) {
				rs = stmt.getResultSet();
			} else {
				if (stmt.getUpdateCount() == -1) {
					// no more results. Must be no resultSet
					break;
				}
			}
		}
		return rs;
	}
	
	/**
	 * 使用{@link TypeHandler}进行返回值类型转换
	 * @param resultSet           返回值来源
	 * @param columnName          返回值列名
	 * @param javaType            需要转换的目标Java类型
	 * @param typeHandlerRegistry 获取{@link TypeHandler}的注册器
	 */
	private Object getObject(ResultSet resultSet, String columnName,
							 TypeHandlerRegistry typeHandlerRegistry,
							 Class<?> javaType) throws SQLException {
		final TypeHandler<?> typeHandler = typeHandlerRegistry.hasTypeHandler(javaType)
												   ? typeHandlerRegistry.getTypeHandler(javaType)
												   : typeHandlerRegistry.getUnknownTypeHandler();
		return typeHandler.getResult(resultSet, columnName);
		
	}
	
	private static class Pair<K, V> {
		
		private K key;
		
		public K getKey() {
			return key;
		}
		
		private V value;
		
		public V getValue() {
			return value;
		}
		
		public Pair() {
			
		}
		
		public Pair(K key, V value) {
			this.key = key;
			this.value = value;
		}
		
	}
	
}

注解类

import java.lang.annotation.*;

/**
 * <p>将查询结果映射成{@link java.util.Map}的注解</p>
 * <p>注:K、V的类型通过{@link org.apache.ibatis.type.TypeHandler}进行类型转换,如有必要可自定义TypeHandler</p>
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MapKeyV {
	
	/**
	 * 放入{@link java.util.Map}中的key的别名
	 */
	String key() default "key";
	
	
	/**
	 * 放入{@link java.util.Map}中的value的别名
	 */
	String val() default "value";
	
	/**
	 * 是否允许key重复
	 */
	boolean allowKeyRepeat() default true;
	
	/**
	 * 对于相同的key,是否允许value不同(若允许则覆盖已存在的值)
	 */
	boolean allowValueRepeat() default false;
	
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值