java 分庫關聯查詢工具類

ciscopuke發表於2021-09-09

    問題:

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

  舉個例子:在查詢"訂單"(位於訂單子系統)列表時,同時需要查詢出所關聯的"使用者"(位於賬戶子系統)的姓名,而這時由於資料儲存在不同的資料來源上,沒有辦法透過一條連表的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://www.cnblogs.com/xiaoxiongcanguan/p/9886730.html  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4369/viewspace-2817865/,如需轉載,請註明出處,否則將追究法律責任。

相關文章