java 分庫關聯查詢工具類

来源:https://www.cnblogs.com/xiaoxiongcanguan/archive/2018/11/01/9886730.html
-Advertisement-
Play Games

問題: 由於公司業務擴大,各個子系統陸續遷移和部署在不同的數據源上,這樣方便擴容,但是因此引出了一些問題。 舉個例子:在查詢"訂單"(位於訂單子系統)列表時,同時需要查詢出所關聯的"用戶"(位於賬戶子系統)的姓名,而這時由於數據存儲在不同的數據源上,沒有辦法通過一條連表的sql獲取到全部的數據,而是 ...


      問題:

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

  舉個例子:在查詢"訂單"(位於訂單子系統)列表時,同時需要查詢出所關聯的"用戶"(位於賬戶子系統)的姓名,而這時由於數據存儲在不同的數據源上,沒有辦法通過一條連表的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。

 

  這是我的第一篇技術博客,無論是排版還是分享的內容上面都還有很多的不足之處,希望大家指出,互相交流,互相進步。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • HTML兩部分組成 head和body ** 在head裡面的標簽就是頭標簽 ** title標簽:表示在標簽上顯示的內容 ** meta標簽:設置頁面的一些相關內容(用的比較少) <meta name="keywords" content="熊貓,金絲猴,恐龍"/> <meta http-equi ...
  • 代理模式詳解 1 什麼是代理模式? 一句話描述:代理模式是一種使用代理對象來執行目標對象的方法併在代理對象中增強目標對象方法的一種設計模式。 詳細描述: 1、理論基礎-代理模式是設計原則中的“開閉原則(對擴展開放、對修改關閉)”的具體實踐,代理對象代為執行目標對象的方法,併在此基礎上進行相應的擴展。 ...
  • ...
  • 最近在Java技術棧微信公眾號粉絲微信群里看到一張圖,說是剛寫完這段下麵這段代碼就被開除了。 開除的原因是因為沒寫註釋嗎? 顯然不是,休眠的邏輯,大家都懂,不需要寫註釋,你註釋寫休眠 1 天也沒意義啊。。。 這個程式員的思維不是一般的牛啊,獲取下一天的日期,居然要休眠等到下一天再獲取,欲哭無淚。。。 ...
  • 集合:{},可變的數據類型,他裡面的元素必須是不可變的數據類型,無序,不重覆。(不重要)集合的書寫 set = {'alex','wusir','ritian','egon','barry'} 增 add update 刪 pop remove clear set 查 for 交集 & inters ...
  • struct的導出和暴露問題 關於struct的導出 struct的屬性是否被導出,也遵循大小寫的原則:首字母大寫的被導出,首字母小寫的不被導出。 所以: 1. 如果struct名稱首字母是小寫的,這個struct不會被導出。連同它裡面的欄位也不會導出,即使有首字母大寫的欄位名 。 2. 如果str ...
  • 多線程 std::lock 當要同時操作2個對象時,就需要同時鎖定這2個對象,而不是先鎖定一個,然後再鎖定另一個。同時鎖定多個對象的方法:std::lock(對象1.鎖,對象2.鎖...) 額外說明:lock_guard\ lock_a(d1.m, std::adopt_lock); 上面這句是為了 ...
  • 1、遞歸與迭代: 遞歸和迭代都是迴圈的一種。簡單地說,遞歸是重覆調用函數自身實現迴圈。迭代是函數內某段代碼實現迴圈,而迭代與普通迴圈的區別是:迴圈代碼中參與運算的變數同時是保存結果的變數,當前保存的結果作為下一次迴圈計算的初始值。 遞歸迴圈中,遇到滿足終止條件的情況時逐層返回來結束。迭代則使用計數器 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...