很多公司(特别是做电商的)其实都是不允许多表关联查询的,或者严格控制关联的表数量,比如最多关联2、3张表。此时,如果某个需求又确实需要进行关联查询怎么办呢?
比如前端有个页面:
很明显,这个页面字段来自两张表:
- t_product
- t_user
正常来说,直接这样写SQL即可:
SELECt p.id, p.product_name, p.price, u.user_name, u.user_age FROM t_product p LEFT JOIN t_user u ON p.user_id=u.id;
但上面说了,不能关联查询。
解决方案作为替代方案,可以先从t_product表查出10条数据到内存(t_product作为主表),然后取出10条数据的uid,再调用UserService#listUser(uids),得到对应的userList。此时内存中有10条product,也有10条user,匹配组合即可。
public ListgetList(Integer page, Integer pageSize) { // 1.查询Product List productList = listProduct(page, pageSize); // 2.取出里面的所有uid List uids = productList.stream() .map(Product::getUid) .collect(Collectors.toList()); // 3.查出userList List userList = listUser(uids); // 4.把List转成Map Map userMap = new HashMap (); for(userList : user){ userMap.put(user.getId(), user); } // 组合并返回数据 List result = new ArrayList<>(); productList.foreach(product->{ ProductExtendsTO productExtends = new ProductExtendsTO(); BeanUtils.copyProperties(product, productExtends); // 根据product的uid从userMap获取user(此处省略user null判断) User user = userMap.get(product.getUid()); productExtends.setUserAge(user.getUserAge()); productExtends.setUserName(user.getUserName()); result.add(productExtends); }); return result; }
上面的代码可以优化为(主要第4点):
public List代码优化:封装ConvertUtilgetList(Integer page, Integer pageSize) { // 1.查询Product List productList = listProduct(page, pageSize); // 2.取出里面的所有uid List uids = productList.stream() .map(Product::getUid) .collect(Collectors.toList()); // 3.查出userList List userList = listUser(uids); // 4.把List转成Map(优化了这里) Map userMap = userList.stream() .collect(Collectors.toMap(User::getId(), user->user)); // 组合并返回数据 List result = new ArrayList<>(); productList.foreach(product->{ ProductExtendsTO productExtends = new ProductExtendsTO(); BeanUtils.copyProperties(product, productExtends); // 根据product的uid从userMap获取user(此处省略user null判断) User user = userMap.get(product.getUid()); productExtends.setUserAge(user.getUserAge()); productExtends.setUserName(user.getUserName()); result.add(productExtends); }) return result; }
List转Map是非常普遍的需求,Stream API其实还是有点啰嗦(代码太长了),所以我们可以试着封装一下:
public final class ConvertUtil {
private ConvertUtil() {
}
public static Map listToMap(List list,
Function keyExtractor) {
if (list == null || list.isEmpty()) {
return new HashMap<>();
}
Map map = new HashMap<>(list.size());
for (V element : list) {
// 利用keyExtractor从对象中抽取Key
K key = keyExtractor.apply(element);
// 这里默认key不能为null
if (key == null) {
continue;
}
map.put(key, element);
}
return map;
}
}
除了List转Map,从List中抽取特定字段的需求也是非常普遍的,比如上面代码:
// 2.取出里面的所有uid(省略null判断) Listuids = productList.stream() .map(Product::getUid) .collect(Collectors.toList());
意思是从productList中抽取uids。为了复用,我们也封装一下:
public class ConvertUtil {
private ConvertUtil() {
}
public static List resultToList(List originList,
Function mapper) {
if (list == null || list.isEmpty()) {
return new ArrayList<>();
}
List newList = new ArrayList<>(originList.size());
for (T originElement : originList) {
R newElement = mapper.apply(originElement);
if (newElement == null) {
continue;
}
newList.add(newElement);
}
return newList;
}
public static Map listToMap(List list,
Function keyExtractor) {
if (list == null || list.isEmpty()) {
return new HashMap<>();
}
Map map = new HashMap<>(list.size());
for (V element : list) {
K key = keyExtractor.apply(element);
if (key == null) {
continue;
}
map.put(key, element);
}
return map;
}
}
上面权当抛砖引玉,大家可以基于实际需求自行扩展ConvertUtil,让它更好用。
总结:- List转Map,重点是传入Map中Key的抽取规则,也就是KeyExtractor,用了函数式接口
- List抽取FieldList,重点也是定义字段的抽取规则,也用了函数式接口
有时遇到复杂的统计报表等数据,很难通过上面“内存关联”的方式完成需求,此时可以让公司的大数据部门提供接口,直接从大数据那边获取数据。但这个并不需要我们操心:小公司适当关联查询无伤大雅,大公司一般都有大数据部门。



