使用方式
- 在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;
}