場景
很多公司(特別是做電商的)其實都是不允許多表關聯查詢的,或者嚴格控制關聯的表數量,比如最多關聯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 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點):
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其實還是有點啰嗦(代碼太長了),所以我們可以試著封裝一下:
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中抽取特定欄位的需求也是非常普遍的,比如上面代碼:
// 2.取出里面的所有uid(省略null判斷)
List<Long> uids = productList.stream()
.map(Product::getUid)
.collect(Collectors.toList());
意思是從productList中抽取uids,為了復用,我們也封裝一下:
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,重點也是定義欄位的抽取規則,也用了函式式介面
其他解決策略
有時遇到復雜的統計報表等資料,很難通過上面“記憶體關聯”的方式完成需求,此時可以讓公司的大資料部門提供介面,直接從大資料那邊獲取資料,但這個并不需要我們操心:小公司適當關聯查詢無傷大雅,大公司一般都有大資料部門,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/381956.html
標籤:其他
