## 問題:
由于公司業(yè)務(wù)擴大,各個子系統(tǒng)陸續(xù)遷移和部署在不同的數(shù)據(jù)源上辅辩,這樣方便擴容难礼,但是因此引出了一些問題娃圆。
舉個例子:在查詢"訂單"(位于訂單子系統(tǒng))列表時,同時需要查詢出所關(guān)聯(lián)的"用戶"(位于賬戶子系統(tǒng))的姓名蛾茉,而這時由于數(shù)據(jù)存儲在不同的數(shù)據(jù)源上讼呢,沒有辦法通過一條連表的sql獲取到全部的數(shù)據(jù),而是必須進行兩次數(shù)據(jù)庫查詢谦炬,從不同的數(shù)據(jù)源分別獲取數(shù)據(jù)悦屏,并且在web服務(wù)器中進行關(guān)聯(lián)映射。在觀察了一段時間后键思,發(fā)現(xiàn)進行關(guān)聯(lián)映射的代碼大部分都是模板化的础爬,因此產(chǎn)生一個想法,想要把這些模板代碼抽象出來吼鳞,簡化開發(fā)看蚜,也增強代碼的可讀性。同時赖条,即使在同一個數(shù)據(jù)源上失乾,如果能將多表聯(lián)查的需求轉(zhuǎn)化為單表多次查詢,也能夠減少代碼的耦合纬乍,同時提高數(shù)據(jù)庫效率。
##? 設(shè)計主要思路:
在關(guān)系型數(shù)據(jù)庫中:
一對一的關(guān)系一般表示為:一方的數(shù)據(jù)表結(jié)構(gòu)中存在一個業(yè)務(wù)上的外鍵關(guān)聯(lián)另一張表的主鍵(訂單和用戶是一對一的關(guān)系裸卫,則訂單表中存在外鍵對應(yīng)于用戶表的主鍵)仿贬。
一對多的關(guān)系一般表示為:多方的數(shù)據(jù)中存在一個業(yè)務(wù)上的外鍵關(guān)聯(lián)一方的主鍵(門店和訂單是一對多的關(guān)系,則訂單表中存在外鍵對應(yīng)于門店的主鍵)墓贿。
而在非關(guān)系型數(shù)據(jù)庫中:
一對一的關(guān)系一般表示為:一方中存在一個屬性茧泪,值為關(guān)聯(lián)的另一方的數(shù)據(jù)對象(訂單和用戶是一對一的關(guān)系,則訂單對象中存在一個用戶屬性)聋袋。
一對多的關(guān)系一般表示為:一方中存在一個屬性队伟,值為關(guān)聯(lián)的另一方的數(shù)據(jù)對象列表(門店和所屬訂單是一對多的關(guān)系,則門店對象表存在一個訂單列表(List)屬性)幽勒。
可以看出java的對象機制嗜侮,天然就支持非關(guān)系型的數(shù)據(jù)模型,因此大概的思路就是啥容,將查詢出來的兩個列表進行符合要求的映射即可锈颗。
**pojo類:**
```
public class OrderForm {
? ? /**
? ? * 主鍵id
? ? * */
? ? private String id;
? ? /**
? ? * 所屬門店id
? ? * */
? ? private String shopID;
? ? /**
? ? * 關(guān)聯(lián)的顧客id
? ? * */
? ? private String customerID;
? ? /**
? ? * 關(guān)聯(lián)的顧客model
? ? * */
? ? private Customer customer;
}
public class Customer {
? ? /**
? ? * 主鍵id
? ? * */
? ? private String id;
? ? /**
? ? * 姓名
? ? * */
? ? private String userName;
}
public class Shop {
? ? /**
? ? * 主鍵id
? ? * */
? ? private String id;
? ? /**
? ? * 門店名
? ? * */
? ? private String shopName;
? ? /**
? ? * 訂單列表 (一個門店關(guān)聯(lián)N個訂單 一對多)
? ? * */
? ? private List<OrderForm> orderFormList;
}
```
輔助工具函數(shù):
```
/***
? ? * 將通過keyName獲得對應(yīng)的bean對象的get方法名稱的字符串
? ? * @param keyName 屬性名
? ? * @return? 返回get方法名稱的字符串
? ? */
? ? private static String makeGetMethodName(String keyName){
? ? ? ? //:::將第一個字母轉(zhuǎn)為大寫
? ? ? ? String newKeyName = transFirstCharUpperCase(keyName);
? ? ? ? return "get" + newKeyName;
? ? }
? ? /***
? ? * 將通過keyName獲得對應(yīng)的bean對象的set方法名稱的字符串
? ? * @param keyName 屬性名
? ? * @return? 返回set方法名稱的字符串
? ? */
? ? private static String makeSetMethodName(String keyName){
? ? ? ? //:::將第一個字母轉(zhuǎn)為大寫
? ? ? ? String newKeyName = transFirstCharUpperCase(keyName);
? ? ? ? return "set" + newKeyName;
? ? }
? ? /**
? ? * 將字符串的第一個字母轉(zhuǎn)為大寫
? ? * @param str 需要被轉(zhuǎn)變的字符串
? ? * @return 返回轉(zhuǎn)變之后的字符串
? ? */
? ? private static String transFirstCharUpperCase(String str){
? ? ? ? return str.replaceFirst(str.substring(0, 1), str.substring(0, 1).toUpperCase());
? ? }
? ? /**
? ? * 判斷當(dāng)前的數(shù)據(jù)是否需要被轉(zhuǎn)換
? ? *
? ? * 兩個列表存在一個為空,則不需要轉(zhuǎn)換
? ? * @return 不需要轉(zhuǎn)換返回 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去重 轉(zhuǎn)為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{
? ? ? ? //:::創(chuàng)建一個map
? ? ? ? Map<String,Object> map = new HashMap<>();
? ? ? ? //:::由keyName獲得對應(yīng)的get方法字符串
? ? ? ? String getMethodName = makeGetMethodName(keyName);
? ? ? ? //:::遍歷beanList
? ? ? ? for(Object obj : beanList){
? ? ? ? ? ? //:::如果當(dāng)前數(shù)據(jù)是hashMap類型
? ? ? ? ? ? if(obj.getClass() == HashMap.class){
? ? ? ? ? ? ? ? Map currentMap = (Map)obj;
? ? ? ? ? ? ? ? //:::使用keyName從map中獲得對應(yīng)的key
? ? ? ? ? ? ? ? String result = (String)currentMap.get(keyName);
? ? ? ? ? ? ? ? //:::放入map中(如果key一樣,則會被覆蓋去重)
? ? ? ? ? ? ? ? map.put(result,currentMap);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? //:::否則默認是pojo對象
? ? ? ? ? ? ? ? //:::獲得get方法
? ? ? ? ? ? ? ? Method getMethod = obj.getClass().getMethod(getMethodName);
? ? ? ? ? ? ? ? //:::通過get方法從bean對象中得到數(shù)據(jù)key
? ? ? ? ? ? ? ? String result = (String)getMethod.invoke(obj);
? ? ? ? ? ? ? ? //:::放入map中(如果key一樣,則會被覆蓋去重)
? ? ? ? ? ? ? ? map.put(result,obj);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //:::返回結(jié)果
? ? ? ? return map;
? ? }
```
**一對一連接接口定義:**
```
/**
? ? ? * 一對一連接 :? beanKeyName <---> dataKeyName 作為連接條件
? ? ? *
? ? ? * @param beanList 需要被存放數(shù)據(jù)的beanList(主體)
? ? ? * @param beanKeyName? beanList中連接字段key的名字
? ? ? * @param beanModelName? beanList中用來存放匹配到的數(shù)據(jù)value的屬性
? ? ? * @param dataList? 需要被關(guān)聯(lián)的data列表
? ? ? * @param dataKeyName 需要被關(guān)聯(lián)的data中連接字段key的名字
? ? ? *
? ? ? * @throws Exception
? ? ? */
? ? public static void oneToOneLinked(List beanList, String beanKeyName, String beanModelName, List dataList, String dataKeyName) throws Exception { }
```
如果帶入上述一對一連接的例子,beanList是訂單列表(List<OrderFrom>)咪惠,beanKeyName是訂單用于關(guān)聯(lián)用戶的字段名稱(例如外鍵“OrderForm.customerID”)击吱,beanModelName是用于存放用戶類的字段名稱("例如OrderForm.customer"),dataList是顧客列表(List<Customer>)遥昧,dataKeyName是被關(guān)聯(lián)數(shù)據(jù)的key(例如主鍵"Customer.id")覆醇。
一對一連接代碼實現(xiàn):
```
/**
? ? * 一對一連接 :? beanKeyName <---> dataKeyName 作為連接條件
? ? *
? ? * @param beanList 需要被存放數(shù)據(jù)的beanList(主體)
? ? * @param beanKeyName? beanList中連接字段key的名字
? ? * @param beanModelName? beanList中用來存放匹配到的數(shù)據(jù)value的屬性
? ? * @param dataList? 需要被關(guān)聯(lián)的data列表
? ? * @param dataKeyName 需要被關(guān)聯(lián)的data中連接字段key的名字
? ? *
? ? * @throws Exception
? ? */
? ? public static void oneToOneLinked(List beanList, String beanKeyName, String beanModelName, List dataList, String dataKeyName) throws Exception {
? ? ? ? //:::如果不需要轉(zhuǎn)換,直接返回
? ? ? ? if(!needTrans(beanList,dataList)){
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? //:::將被關(guān)聯(lián)的數(shù)據(jù)列表,以需要連接的字段為key,轉(zhuǎn)換成map,加快查詢的速度
? ? ? ? Map<String,Object> dataMap = beanListToMap(dataList,dataKeyName);
? ? ? ? //:::進行數(shù)據(jù)匹配連接
? ? matchedDataToBeanList(beanList,beanKeyName,beanModelName,dataMap);
}
/**
? ? * 將批量查詢出來的數(shù)據(jù)集合,組裝到對應(yīng)的beanList之中
? ? * @param beanList 需要被存放數(shù)據(jù)的beanList(主體)
? ? * @param beanKeyName? beanList中用來匹配數(shù)據(jù)的屬性
? ? * @param beanModelName? beanList中用來存放匹配到的數(shù)據(jù)的屬性
? ? * @param dataMap? data結(jié)果集以某一字段作為key對應(yīng)的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);
? ? ? ? ? ? //:::調(diào)用獲得當(dāng)前的key
? ? ? ? ? ? String currentBeanKey = (String)beanGetMethod.invoke(bean);
? ? ? ? ? ? //:::從被關(guān)聯(lián)的數(shù)據(jù)集map中找到匹配的數(shù)據(jù)
? ? ? ? ? ? Object matchedData = dataMap.get(currentBeanKey);
? ? ? ? ? ? //:::如果找到了匹配的對象
? ? ? ? ? ? if(matchedData != null){
? ? ? ? ? ? ? ? //:::獲得bean中對應(yīng)model的set方法
? ? ? ? ? ? ? ? Class clazz = matchedData.getClass();
? ? ? ? ? ? ? ? //:::如果匹配到的數(shù)據(jù)是hashMap
? ? ? ? ? ? ? ? if(clazz == HashMap.class){
? ? ? ? ? ? ? ? ? ? //:::轉(zhuǎn)為父類map class用來調(diào)用set方法
? ? ? ? ? ? ? ? ? ? clazz = Map.class;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //:::獲得主體bean用于存放被關(guān)聯(lián)對象的set方法
? ? ? ? ? ? ? ? Method beanSetMethod = bean.getClass().getMethod(beanSetMethodName,clazz);
? ? ? ? ? ? ? ? //:::執(zhí)行set方法,將匹配到的數(shù)據(jù)放入主體數(shù)據(jù)對應(yīng)的model屬性中
? ? ? ? ? ? ? ? beanSetMethod.invoke(bean,matchedData);
? ? ? ? ? ? }
? ? ? ? }
? ? }
```
一對多連接接口定義:
```
/**
? ? * 一對多連接 :? oneKeyName <---> manyKeyName 作為連接條件
? ? *
? ? * @param oneDataList? ? ? '一方' 數(shù)據(jù)列表
? ? * @param oneKeyName? ? ? ? '一方' 連接字段key的名字
? ? * @param oneModelName? ? ? '一方' 用于存放 '多方'數(shù)據(jù)的列表屬性名
? ? * @param manyDataList? ? ? '多方' 數(shù)據(jù)列表
? ? * @param manyKeyName? ? ? '多方' 連接字段key的名字
? ? *
? ? *? 注意:? '一方' 存放 '多方'數(shù)據(jù)的屬性oneModelName類型必須為List
? ? *
? ? * @throws Exception
? ? */
? ? public static void oneToManyLinked(List oneDataList,String oneKeyName,String oneModelName,List manyDataList,String manyKeyName) throws Exception {}
```
如果帶入上述一對多連接的例子朵纷,oneDataList是門店列表(List<Shop>),oneKeyName是門店用于關(guān)聯(lián)訂單的字段名稱(例如主鍵“Shop.id”)永脓,oneModelName是用于存放訂單列表的字段名稱(例如"Shop.orderFomrList")袍辞,manyDataList是多方列表(List<OrderForm>),manyKeyName是被關(guān)聯(lián)數(shù)據(jù)的key(例如外鍵"OrderFrom.shopID")憨奸。
一對多連接代碼實現(xiàn):
```
/**
? ? * 一對多連接 :? oneKeyName <---> manyKeyName 作為連接條件
? ? *
? ? * @param oneDataList? ? ? '一方' 數(shù)據(jù)列表
? ? * @param oneKeyName? ? ? ? '一方' 連接字段key的名字
? ? * @param oneModelName? ? ? '一方' 用于存放 '多方'數(shù)據(jù)的列表屬性名
? ? * @param manyDataList? ? ? '多方' 數(shù)據(jù)列表
? ? * @param manyKeyName? ? ? '多方' 連接字段key的名字
? ? *
? ? *? 注意:? '一方' 存放 '多方'數(shù)據(jù)的屬性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;
? ? ? ? }
? ? ? ? //:::將'一方'數(shù)據(jù),以連接字段為key,轉(zhuǎn)成map,便于查詢
? ? ? ? Map<String,Object> oneDataMap = beanListToMap(oneDataList,oneKeyName);
? ? ? ? //:::獲得'一方'存放 '多方'數(shù)據(jù)字段的get方法名
? ? ? ? String oneDataModelGetMethodName = makeGetMethodName(oneModelName);
? ? ? ? //:::獲得'一方'存放 '多方'數(shù)據(jù)字段的set方法名
? ? ? ? String oneDataModelSetMethodName = makeSetMethodName(oneModelName);
? ? ? ? //:::獲得'多方'連接字段的get方法名
? ? ? ? String manyDataKeyGetMethodName = makeGetMethodName(manyKeyName);
? ? ? ? try {
? ? ? ? ? ? //:::遍歷'多方'列表
? ? ? ? ? ? for (Object manyDataItem : manyDataList) {
? ? ? ? ? ? ? ? //:::'多方'對象連接key的值
? ? ? ? ? ? ? ? String manyDataItemKey;
? ? ? ? ? ? ? ? //:::判斷當(dāng)前'多方'對象的類型是否是 hashMap
? ? ? ? ? ? ? ? if(manyDataItem.getClass() == HashMap.class){
? ? ? ? ? ? ? ? ? ? //:::如果是hashMap類型的,先轉(zhuǎn)為Map對象
? ? ? ? ? ? ? ? ? ? Map manyDataItemMap = (Map)manyDataItem;
? ? ? ? ? ? ? ? ? ? //:::通過參數(shù)key 直接獲取對象key連接字段的值
? ? ? ? ? ? ? ? ? ? manyDataItemKey = (String)manyDataItemMap.get(manyKeyName);
? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? //:::如果是普通的pojo對象,則通過反射獲得get方法來獲取key連接字段的值
? ? ? ? ? ? ? ? ? ? //:::獲得'多方'數(shù)據(jù)中key的method對象
? ? ? ? ? ? ? ? ? ? Method manyDataKeyGetMethod = manyDataItem.getClass().getMethod(manyDataKeyGetMethodName);
? ? ? ? ? ? ? ? ? ? //:::調(diào)用'多方'數(shù)據(jù)的get方法獲得當(dāng)前'多方'數(shù)據(jù)連接字段key的值
? ? ? ? ? ? ? ? ? ? manyDataItemKey = (String) manyDataKeyGetMethod.invoke(manyDataItem);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? //:::通過'多方'的連接字段key從 '一方' map集合中查找出連接key相同的 '一方'數(shù)據(jù)對象
? ? ? ? ? ? ? ? Object matchedOneData = oneDataMap.get(manyDataItemKey);
? ? ? ? ? ? ? ? //:::如果匹配到了數(shù)據(jù),才進行操作
? ? ? ? ? ? ? ? if(matchedOneData != null){
? ? ? ? ? ? ? ? ? ? //:::將當(dāng)前迭代的 '多方'數(shù)據(jù) 放入 '一方' 的對應(yīng)的列表中
? ? ? ? ? ? ? ? ? ? setManyDataToOne(matchedOneData,manyDataItem,oneDataModelGetMethodName,oneDataModelSetMethodName);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }catch(Exception e){
? ? ? ? ? ? throw new Exception(e);
? ? ? ? }
? ? }
/**
? ? * 將 '多方' 數(shù)據(jù)存入 '一方' 列表中
? ? * @param oneData 匹配到的'一方'數(shù)據(jù)
? ? * @param manyDataItem? 當(dāng)前迭代的 '多方數(shù)據(jù)'
? ? * @param oneDataModelGetMethodName 一方列表的get方法名
? ? * @param oneDataModelSetMethodName 一方列表的set方法名
? ? * @throws Exception
? ? */
? ? private static void setManyDataToOne(Object oneData,Object manyDataItem,String oneDataModelGetMethodName,String oneDataModelSetMethodName) throws Exception {
? ? ? ? //:::獲得 '一方' 數(shù)據(jù)中存放'多方'數(shù)據(jù)屬性的get方法
? ? ? ? Method oneDataModelGetMethod = oneData.getClass().getMethod(oneDataModelGetMethodName);
? ? ? ? //::: '一方' 數(shù)據(jù)中存放'多方'數(shù)據(jù)屬性的set方法
? ? ? ? Method oneDataModelSetMethod;
? ? ? ? try {
? ? ? ? ? ? //::: '一方' set方法對象
? ? ? ? ? ? oneDataModelSetMethod = oneData.getClass().getMethod(oneDataModelSetMethodName,List.class);
? ? ? ? }catch(NoSuchMethodException e){
? ? ? ? ? ? throw new Exception("未找到滿足條件的'一方'set方法");
? ? ? ? }
? ? ? ? //:::獲得存放'多方'數(shù)據(jù)get方法返回值類型
? ? ? ? Class modelType = oneDataModelGetMethod.getReturnType();
? ? ? ? //::: get方法返回值必須是List
? ? ? ? if(modelType.equals(List.class)){
? ? ? ? ? ? //:::調(diào)用get方法,獲得數(shù)據(jù)列表
? ? ? ? ? ? List modelList = (List)oneDataModelGetMethod.invoke(oneData);
? ? ? ? ? ? //:::如果當(dāng)前成員變量為null
? ? ? ? ? ? if(modelList == null){
? ? ? ? ? ? ? ? //:::創(chuàng)建一個新的List
? ? ? ? ? ? ? ? List newList = new ArrayList<>();
? ? ? ? ? ? ? ? //:::將當(dāng)前的'多方'數(shù)據(jù)存入list
? ? ? ? ? ? ? ? newList.add(manyDataItem);
? ? ? ? ? ? ? ? //:::將這個新創(chuàng)建出的List賦值給 '一方'的對象
? ? ? ? ? ? ? ? oneDataModelSetMethod.invoke(oneData,newList);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? //:::如果已經(jīng)存在了List
? ? ? ? ? ? ? ? //:::直接將'多方'數(shù)據(jù)存入list
? ? ? ? ? ? ? ? modelList.add(manyDataItem);
? ? ? ? ? ? }
? ? ? ? }else{
? ? ? ? ? ? throw new Exception("一對多連接時,一方指定的model對象必須是list類型");
? ? ? ? }
? ? }
```
測試用例在我的github上面 https://github.com/1399852153/linkedQueryUtil革屠。