公司禁止JOIN查詢怎麼辦?

場景

不少公司(特別是作電商的)其實都是不容許多表關聯查詢的,或者嚴格控制關聯的表數量,好比最多關聯二、3張表。此時,若是某個需求又確實須要進行關聯查詢怎麼辦呢?前端

好比前端有個頁面:java

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

很明顯,這個頁面字段來自兩張表:sql

  • t_product
  • t_user

正常來講,直接這樣寫SQL便可:markdown

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;
複製代碼

但上面說了,不能關聯查詢。app

解決方案

做爲替代方案,能夠先從t_product表查出10條數據到內存(t_product做爲主表),而後取出10條數據的uid,再調用UserService#listUser(uids),獲得對應的userList。此時內存中有10條product,也有10條user,匹配組合便可。iphone

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中抽取特定字段的需求也是很是廣泛的,好比上面代碼:post

// 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,重點也是定義字段的抽取規則,也用了函數式接口

其餘解決策略

有時遇到複雜的統計報表等數據,很難經過上面「內存關聯」的方式完成需求,此時可讓公司的大數據部門提供接口,直接從大數據那邊獲取數據。但這個並不須要咱們操心:小公司適當關聯查詢無傷大雅,大公司通常都有大數據部門。

五一假期最後一天,收拾收拾心情準備回杭搬磚。

我是bravo1988,下次見。

よろしく・つづく

我昨晚夢見你了.gif

往期文章:

漫畫:從JVM鎖扯到Redis分佈式鎖

深刻淺出Java線程基礎

深刻淺出Java註解

Tomcat外傳:孤獨的小貓咪

相關文章
相關標籤/搜索