事件背景:
现有代码是两个List列表循环遍历做对比,如果两个列表的长度较大时,则时间复杂度为O(n*m),复杂度较高。其中一个列表是在数据库中查找的List<String> 形式,另一个是调接口返回的List<Object>形式,现考虑在数据库获取数据时,返回一个map,通过map.get(key),可直接对比,时间复杂度降为O(n)。之前做过mybatis返回List<Map<>>的方法,但对直接返回map不怎么了解,网上查阅资料,是使用@MapKey注解实现,在这里记录一下,以防忘记。
1、List<Map<String,String>>形式
使用下面的代码,如果返回多条记录,即有多个("123":“test”)、("124":"test1"),则MyBatis就会报错,因为MyBatis是把结果以("id":123)、("name":"Jack")的形式保存在Map中的;而如果返回只有一条包括了id和name的记录就没问题。
mapper:
Map<String,String> getUserIdToMap();
xml:
<select id="getUserIdToMap" resultType="java.util.Map">
select id,name
from manage_user_baseinfo
where status = '在职'
</select>
2、解决方法
在mapper中,再加一个Map,并使用@MapKey注解
mapper:
@MapKey("id")
Map<String, Map<String, String>> getUserIdToMap();
xml:
<select id="getUserIdToMap" resultType="java.util.Map">
select id,name
from manage_user_baseinfo
where status = '在职'
</select>
3、@MapKey初探
更新:2019-06-13
先来看execute方法,代码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
这个方法是mybatis具体的执行方法,可以看到,增删改查对应不同情况,这里我们的查询会走 SELECT分支的executeForMap方法,进去看是怎么实现的,代码如下,
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
这里,convertArgsToSqlCommandParam是参数相关,RowBounds与分页相关,先不管,其中method.getmapKey就是注解中的key值,继续往下走,看selectMap方法,
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<V>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
可以看到,查询主要是selectList(statement,parameter,rowBounds)方法,返回的是List<Map>类型,这里不讲(这里我还没看……),这个方法没有用到mapKey,接着向下看,应该是
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
这个for循环在起作用。这里,nextResultObject()方法是一个类似于初始化的工作,继续看handleResult()方法,
@Override
public void handleResult(ResultContext<? extends V> context) {
final V value = context.getResultObject();
final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
// TODO is that assignment always true?
final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);
}
这里的value对象类型为Map,MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory)这句应该是将Map对象转成MetaObject对象,然后通过mapKey取出对应属性的值。进getValue(mapKey)方法,
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}
这里通过反射拿到id对应的值,然后上一层放入mapperResults中,最后在selectMap方法中返回getMapperResults(),得到Map形式的返回结果。
(有想法把mybatis的源代码看一遍,不知道我这个懒癌加拖延症重度患者能不能看完……)