java 分庫關聯查詢工具類

問題:

  由於公司業務擴大,各個子系統陸續遷移和部署在不同的數據源上,這樣方便擴容,但是因此引出了一些問題。

  舉個例子:在查詢"訂單"(位於訂單子系統)列表時,同時需要查詢出所關聯的"用戶"(位於賬戶子系統)的姓名,而這時由於數據存儲在不同的數據源上,沒有辦法通過一條連表的sql獲取到全部的數據,而是必須進行兩次數據庫查詢,從不同的數據源分別獲取數據,並且在web服務器中進行關聯映射。在觀察了一段時間後,發現進行關聯映射的代碼大部分都是模板化的,因此產生一個想法,想要把這些模板代碼抽象出來,簡化開發,也增強代碼的可讀性。同時,即使在同一個數據源上,如果能將多表聯查的需求轉化爲單表多次查詢,也能夠減少代碼的耦合,同時提高數據庫效率。

設計主要思路:

在關係型數據庫中:

  一對一的關係一般表示爲:一方的數據表結構中存在一個業務上的外鍵關聯另一張表的主鍵(訂單和用戶是一對一的關係,則訂單表中存在外鍵對應於用戶表的主鍵)。

  一對多的關係一般表示爲:多方的數據中存在一個業務上的外鍵關聯一方的主鍵(門店和訂單是一對多的關係,則訂單表中存在外鍵對應於門店的主鍵)。

而在非關係型數據庫中:

  一對一的關係一般表示爲:一方中存在一個屬性,值爲關聯的另一方的數據對象(訂單和用戶是一對一的關係,則訂單對象中存在一個用戶屬性)。

  一對多的關係一般表示爲:一方中存在一個屬性,值爲關聯的另一方的數據對象列表(門店和所屬訂單是一對多的關係,則門店對象表存在一個訂單列表(List)屬性)。

  可以看出java的對象機制,天然就支持非關係型的數據模型,因此大概的思路就是,將查詢出來的兩個列表進行符合要求的映射即可。

  pojo類:
  

public class OrderForm {
    /**
     * 主鍵id
     * */
    private String id;
    /**
     * 所屬門店id
     * */
    private String shopID;
    /**
     * 關聯的顧客id
     * */
    private String customerID;
    /**
     * 關聯的顧客model
     * */
    private Customer customer;
}

public class Customer {
    /**
     * 主鍵id
     * */
    private String id;
    /**
     * 姓名
     * */
    private String userName;
}

public class Shop {
    /**
     * 主鍵id
     * */
    private String id;
    /**
     * 門店名
     * */
    private String shopName;
    /**
     * 訂單列表 (一個門店關聯N個訂單 一對多)
     * */
    private List<OrderForm> orderFormList;
}

  輔助工具函數:
  

/***
     * 將通過keyName獲得對應的bean對象的get方法名稱的字符串
     * @param keyName 屬性名
     * @return  返回get方法名稱的字符串
     */
    private static String makeGetMethodName(String keyName){
        //:::將第一個字母轉爲大寫
        String newKeyName = transFirstCharUpperCase(keyName);

        return "get" + newKeyName;
    }

    /***
     * 將通過keyName獲得對應的bean對象的set方法名稱的字符串
     * @param keyName 屬性名
     * @return  返回set方法名稱的字符串
     */
    private static String makeSetMethodName(String keyName){
        //:::將第一個字母轉爲大寫
        String newKeyName = transFirstCharUpperCase(keyName);

        return "set" + newKeyName;
    }

    /**
     * 將字符串的第一個字母轉爲大寫
     * @param str 需要被轉變的字符串
     * @return 返回轉變之後的字符串
     */
    private static String transFirstCharUpperCase(String str){
        return str.replaceFirst(str.substring(0, 1), str.substring(0, 1).toUpperCase());
    }

    /**
     * 判斷當前的數據是否需要被轉換
     *
     * 兩個列表存在一個爲空,則不需要轉換
     * @return 不需要轉換返回 false,需要返回 true
     * */
    private static boolean needTrans(List beanList,List dataList){
        if(listIsEmpty(beanList) || listIsEmpty(dataList)){
            return false;
        }else{
            return true;
        }
    }

    /**
     * 列表是否爲空
     * */
    private static boolean listIsEmpty(List list){
        if(list == null || list.isEmpty()){
            return true;
        }else{
            return false;
        }
    }

/**
     * 將javaBean組成的list去重 轉爲map, key爲bean中指定的一個屬性
     *
     * @param beanList list 本身
     * @param keyName 生成的map中的key
     * @return
     * @throws Exception
     */
    public static Map<String,Object> beanListToMap(List beanList,String keyName) throws Exception{
        //:::創建一個map
        Map<String,Object> map = new HashMap<>();

        //:::由keyName獲得對應的get方法字符串
        String getMethodName = makeGetMethodName(keyName);

        //:::遍歷beanList
        for(Object obj : beanList){
            //:::如果當前數據是hashMap類型
            if(obj.getClass() == HashMap.class){
                Map currentMap = (Map)obj;

                //:::使用keyName從map中獲得對應的key
                String result = (String)currentMap.get(keyName);

                //:::放入map中(如果key一樣,則會被覆蓋去重)
                map.put(result,currentMap);
            }else{
                //:::否則默認是pojo對象
                //:::獲得get方法
                Method getMethod = obj.getClass().getMethod(getMethodName);

                //:::通過get方法從bean對象中得到數據key
                String result = (String)getMethod.invoke(obj);

                //:::放入map中(如果key一樣,則會被覆蓋去重)
                map.put(result,obj);
            }
        }
        //:::返回結果
        return map;
    }

一對一連接接口定義:

/**
      * 一對一連接 :  beanKeyName <---> dataKeyName 作爲連接條件
      *
      * @param beanList 需要被存放數據的beanList(主體)
      * @param beanKeyName   beanList中連接字段key的名字
      * @param beanModelName  beanList中用來存放匹配到的數據value的屬性
      * @param dataList  需要被關聯的data列表
      * @param dataKeyName 需要被關聯的data中連接字段key的名字
      *
      * @throws Exception
      */
     public static void oneToOneLinked(List beanList, String beanKeyName, String beanModelName, List dataList, String dataKeyName) throws Exception { }

如果帶入上述一對一連接的例子,beanList是訂單列表(List<OrderFrom>),beanKeyName是訂單用於關聯用戶的字段名稱(例如外鍵“OrderForm.customerID”),beanModelName是用於存放用戶類的字段名稱("例如OrderForm.customer"),dataList是顧客列表(List<Customer>),dataKeyName是被關聯數據的key(例如主鍵"Customer.id")。

  一對一連接代碼實現:
  

/**
     * 一對一連接 :  beanKeyName <---> dataKeyName 作爲連接條件
     *
     * @param beanList 需要被存放數據的beanList(主體)
     * @param beanKeyName   beanList中連接字段key的名字
     * @param beanModelName  beanList中用來存放匹配到的數據value的屬性
     * @param dataList  需要被關聯的data列表
     * @param dataKeyName 需要被關聯的data中連接字段key的名字
     *
     * @throws Exception
     */
    public static void oneToOneLinked(List beanList, String beanKeyName, String beanModelName, List dataList, String dataKeyName) throws Exception {
        //:::如果不需要轉換,直接返回
        if(!needTrans(beanList,dataList)){
            return;
        }
        //:::將被關聯的數據列表,以需要連接的字段爲key,轉換成map,加快查詢的速度
        Map<String,Object> dataMap = beanListToMap(dataList,dataKeyName);

        //:::進行數據匹配連接
       matchedDataToBeanList(beanList,beanKeyName,beanModelName,dataMap);
  }

/**
     * 將批量查詢出來的數據集合,組裝到對應的beanList之中
     * @param beanList 需要被存放數據的beanList(主體)
     * @param beanKeyName   beanList中用來匹配數據的屬性
     * @param beanModelName  beanList中用來存放匹配到的數據的屬性
     * @param dataMap  data結果集以某一字段作爲key對應的map
     * @throws Exception
     */
    private static void matchedDataToBeanList(List beanList, String beanKeyName, String beanModelName, Map<String,Object> dataMap) throws Exception {
        //:::獲得beanList中存放對象的key的get方法名
        String beanGetMethodName = makeGetMethodName(beanKeyName);
        //:::獲得beanList中存放對象的model的set方法名
        String beanSetMethodName = makeSetMethodName(beanModelName);

        //:::遍歷整個beanList
        for(Object bean : beanList){
            //:::獲得bean中key的method對象
            Method beanGetMethod = bean.getClass().getMethod(beanGetMethodName);

            //:::調用獲得當前的key
            String currentBeanKey = (String)beanGetMethod.invoke(bean);

            //:::從被關聯的數據集map中找到匹配的數據
            Object matchedData = dataMap.get(currentBeanKey);

            //:::如果找到了匹配的對象
            if(matchedData != null){
                //:::獲得bean中對應model的set方法
                Class clazz = matchedData.getClass();

                //:::如果匹配到的數據是hashMap
                if(clazz == HashMap.class){
                    //:::轉爲父類map class用來調用set方法
                    clazz = Map.class;
                }

                //:::獲得主體bean用於存放被關聯對象的set方法
                Method beanSetMethod = bean.getClass().getMethod(beanSetMethodName,clazz);
                //:::執行set方法,將匹配到的數據放入主體數據對應的model屬性中
                beanSetMethod.invoke(bean,matchedData);
            }
        }
    }

一對多連接接口定義:

/**
     * 一對多連接 :  oneKeyName <---> manyKeyName 作爲連接條件
     *
     * @param oneDataList       '一方' 數據列表
     * @param oneKeyName        '一方' 連接字段key的名字
     * @param oneModelName      '一方' 用於存放 '多方'數據的列表屬性名
     * @param manyDataList      '多方' 數據列表
     * @param manyKeyName       '多方' 連接字段key的名字
     *
     *  注意:  '一方' 存放 '多方'數據的屬性oneModelName類型必須爲List
     *
     * @throws Exception
     */
    public static void oneToManyLinked(List oneDataList,String oneKeyName,String oneModelName,List manyDataList,String manyKeyName) throws Exception {}

如果帶入上述一對多連接的例子,oneDataList是門店列表(List<Shop>),oneKeyName是門店用於關聯訂單的字段名稱(例如主鍵“Shop.id”),oneModelName是用於存放訂單列表的字段名稱(例如"Shop.orderFomrList"),manyDataList是多方列表(List<OrderForm>),manyKeyName是被關聯數據的key(例如外鍵"OrderFrom.shopID")。

  一對多連接代碼實現:
  

/**
     * 一對多連接 :  oneKeyName <---> manyKeyName 作爲連接條件
     *
     * @param oneDataList       '一方' 數據列表
     * @param oneKeyName        '一方' 連接字段key的名字
     * @param oneModelName      '一方' 用於存放 '多方'數據的列表屬性名
     * @param manyDataList      '多方' 數據列表
     * @param manyKeyName       '多方' 連接字段key的名字
     *
     *  注意:  '一方' 存放 '多方'數據的屬性oneModelName類型必須爲List
     *
     * @throws Exception
     */
    public static void oneToManyLinked(List oneDataList,String oneKeyName,String oneModelName,List manyDataList,String manyKeyName) throws Exception {
        if(!needTrans(oneDataList,manyDataList)){
            return;
        }
        //:::將'一方'數據,以連接字段爲key,轉成map,便於查詢
        Map<String,Object> oneDataMap = beanListToMap(oneDataList,oneKeyName);

        //:::獲得'一方'存放 '多方'數據字段的get方法名
        String oneDataModelGetMethodName = makeGetMethodName(oneModelName);
        //:::獲得'一方'存放 '多方'數據字段的set方法名
        String oneDataModelSetMethodName = makeSetMethodName(oneModelName);

        //:::獲得'多方'連接字段的get方法名
        String manyDataKeyGetMethodName = makeGetMethodName(manyKeyName);

        try {
            //:::遍歷'多方'列表
            for (Object manyDataItem : manyDataList) {
                //:::'多方'對象連接key的值
                String manyDataItemKey;
                //:::判斷當前'多方'對象的類型是否是 hashMap
                if(manyDataItem.getClass() == HashMap.class){
                    //:::如果是hashMap類型的,先轉爲Map對象
                    Map manyDataItemMap = (Map)manyDataItem;

                    //:::通過參數key 直接獲取對象key連接字段的值
                    manyDataItemKey = (String)manyDataItemMap.get(manyKeyName);
                }else{
                    //:::如果是普通的pojo對象,則通過反射獲得get方法來獲取key連接字段的值

                    //:::獲得'多方'數據中key的method對象
                    Method manyDataKeyGetMethod = manyDataItem.getClass().getMethod(manyDataKeyGetMethodName);
                    //:::調用'多方'數據的get方法獲得當前'多方'數據連接字段key的值
                    manyDataItemKey = (String) manyDataKeyGetMethod.invoke(manyDataItem);
                }
                //:::通過'多方'的連接字段key從 '一方' map集合中查找出連接key相同的 '一方'數據對象
                Object matchedOneData = oneDataMap.get(manyDataItemKey);

                //:::如果匹配到了數據,才進行操作
                if(matchedOneData != null){
                    //:::將當前迭代的 '多方'數據 放入 '一方' 的對應的列表中
                    setManyDataToOne(matchedOneData,manyDataItem,oneDataModelGetMethodName,oneDataModelSetMethodName);
                }
            }
        }catch(Exception e){
            throw new Exception(e);
        }
    }

 /**
     * 將 '多方' 數據存入 '一方' 列表中
     * @param oneData 匹配到的'一方'數據
     * @param manyDataItem  當前迭代的 '多方數據'
     * @param oneDataModelGetMethodName 一方列表的get方法名
     * @param oneDataModelSetMethodName 一方列表的set方法名
     * @throws Exception
     */
    private static void setManyDataToOne(Object oneData,Object manyDataItem,String oneDataModelGetMethodName,String oneDataModelSetMethodName) throws Exception {
        //:::獲得 '一方' 數據中存放'多方'數據屬性的get方法
        Method oneDataModelGetMethod = oneData.getClass().getMethod(oneDataModelGetMethodName);
        //::: '一方' 數據中存放'多方'數據屬性的set方法
        Method oneDataModelSetMethod;
        try {
            //::: '一方' set方法對象
            oneDataModelSetMethod = oneData.getClass().getMethod(oneDataModelSetMethodName,List.class);
        }catch(NoSuchMethodException e){
            throw new Exception("未找到滿足條件的'一方'set方法");
        }

        //:::獲得存放'多方'數據get方法返回值類型
        Class modelType = oneDataModelGetMethod.getReturnType();
        //::: get方法返回值必須是List
        if(modelType.equals(List.class)){
            //:::調用get方法,獲得數據列表
            List modelList = (List)oneDataModelGetMethod.invoke(oneData);

            //:::如果當前成員變量爲null
            if(modelList == null){
                //:::創建一個新的List
                List newList = new ArrayList<>();
                //:::將當前的'多方'數據存入list
                newList.add(manyDataItem);
                //:::將這個新創建出的List賦值給 '一方'的對象
                oneDataModelSetMethod.invoke(oneData,newList);
            }else{
                //:::如果已經存在了List

                //:::直接將'多方'數據存入list
                modelList.add(manyDataItem);
            }
        }else{
            throw new Exception("一對多連接時,一方指定的model對象必須是list類型");
        }
    }

測試用例在我的github上面 https://github.com/1399852153/linkedQueryUtil

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章