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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...