公司禁止JOIN查询怎么办? 场景 解决方案 代码优化:封装

场景

很多公司(特别是做电商的)其实都是不允许多表关联查询的,或者严格控制关联的表数量,比如最多关联2、3张表。此时,如果某个需求又确实需要进行关联查询怎么办呢?

比如前端有个页面:

id product_name price user_name user_age
10086 iphone 12 pro 6666 zhangsan 18

很明显,这个页面字段来自两张表:

  • t_product
  • t_user

正常来说,直接这样写SQL即可:

1
2
3
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,匹配组合即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码public List<ProductExtendsTO> getList(Integer page, Integer pageSize) {
// 1.查询Product
List<Product> productList = listProduct(page, pageSize);
// 2.取出里面的所有uid
List<Long> uids = productList.stream()
.map(Product::getUid)
.collect(Collectors.toList());
// 3.查出userList
List<User> userList = listUser(uids);
// 4.把List转成Map
Map<Long, User> userMap = new HashMap<Long, User>();
for(userList : user){
userMap.put(user.getId(), user);
}

// 组合并返回数据
List<ProductExtendsTO> 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点):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码public List<ProductExtendsTO> getList(Integer page, Integer pageSize) {
// 1.查询Product
List<Product> productList = listProduct(page, pageSize);
// 2.取出里面的所有uid
List<Long> uids = productList.stream()
.map(Product::getUid)
.collect(Collectors.toList());
// 3.查出userList
List<User> userList = listUser(uids);
// 4.把List转成Map(优化了这里)
Map<Long, User> userMap = userList.stream()
.collect(Collectors.toMap(User::getId(), user->user));

// 组合并返回数据
List<ProductExtendsTO> 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;
}

代码优化:封装ConvertUtil

List转Map是非常普遍的需求,Stream API其实还是有点啰嗦(代码太长了),所以我们可以试着封装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
java复制代码public final class ConvertUtil {

private ConvertUtil() {
}

/**
* 将List转为Map
*
* @param list 原数据
* @param keyExtractor Key的抽取规则
* @param <K> Key
* @param <V> Value
* @return
*/
public static <K, V> Map<K, V> listToMap(List<V> list,
Function<V, K> keyExtractor) {
if (list == null || list.isEmpty()) {
return new HashMap<>();
}

Map<K, V> 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中抽取特定字段的需求也是非常普遍的,比如上面代码:

1
2
3
4
java复制代码// 2.取出里面的所有uid(省略null判断)
List<Long> uids = productList.stream()
.map(Product::getUid)
.collect(Collectors.toList());

意思是从productList中抽取uids。为了复用,我们也封装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
java复制代码public class ConvertUtil {

private ConvertUtil() {
}

/**
* 将List映射为List,比如List<Person> personList转为List<String> nameList
*
* @param originList 原数据
* @param mapper 映射规则
* @param <T> 原数据的元素类型
* @param <R> 新数据的元素类型
* @return
*/
public static <T, R> List<R> resultToList(List<T> originList,
Function<T, R> mapper) {
if (list == null || list.isEmpty()) {
return new ArrayList<>();
}

List<R> newList = new ArrayList<>(originList.size());
for (T originElement : originList) {
R newElement = mapper.apply(originElement);
if (newElement == null) {
continue;
}
newList.add(newElement);
}

return newList;
}

/**
* 将List转为Map
*
* @param list 原数据
* @param keyExtractor Key的抽取规则
* @param <K> Key
* @param <V> Value
* @return
*/
public static <K, V> Map<K, V> listToMap(List<V> list,
Function<V, K> keyExtractor) {
if (list == null || list.isEmpty()) {
return new HashMap<>();
}

Map<K, V> 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,重点也是定义字段的抽取规则,也用了函数式接口

其他解决策略

有时遇到复杂的统计报表等数据,很难通过上面“内存关联”的方式完成需求,此时可以让公司的大数据部门提供接口,直接从大数据那边获取数据。但这个并不需要我们操心:小公司适当关联查询无伤大雅,大公司一般都有大数据部门。

五一假期最后一天,收拾收拾心情准备回杭搬砖。

我是bravo1988,下次见。

よろしく・つづく

我昨晚梦见你了.gif

往期文章:

漫画:从JVM锁扯到Redis分布式锁

深入浅出Java线程基础

深入浅出Java注解

Tomcat外传:孤独的小猫咪

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%