因爲公司業務擴大,各個子系統陸續遷移和部署在不一樣的數據源上,這樣方便擴容,可是所以引出了一些問題。java
舉個例子:在查詢"訂單"(位於訂單子系統)列表時,同時須要查詢出所關聯的"用戶"(位於帳戶子系統)的姓名,而這時因爲數據存儲在不一樣的數據源上,沒有辦法經過一條連表的sql獲取到所有的數據,而是必須進行兩次數據庫查詢,從不一樣的數據源分別獲取數據,而且在web服務器中進行關聯映射。在觀察了一段時間後,發現進行關聯映射的代碼大部分都是模板化的,所以產生一個想法,想要把這些模板代碼抽象出來,簡化開發,也加強代碼的可讀性。同時,即便在同一個數據源上,若是能將多表聯查的需求轉化爲單表屢次查詢,也可以減小代碼的耦合,同時提升數據庫效率。git
在關係型數據庫中:github
一對一的關係通常表示爲:一方的數據表結構中存在一個業務上的外鍵關聯另外一張表的主鍵(訂單和用戶是一對一的關係,則訂單表中存在外鍵對應於用戶表的主鍵)。web
一對多的關係通常表示爲:多方的數據中存在一個業務上的外鍵關聯一方的主鍵(門店和訂單是一對多的關係,則訂單表中存在外鍵對應於門店的主鍵)。sql
而在非關係型數據庫中:數據庫
一對一的關係通常表示爲:一方中存在一個屬性,值爲關聯的另外一方的數據對象(訂單和用戶是一對一的關係,則訂單對象中存在一個用戶屬性)。bash
一對多的關係通常表示爲:一方中存在一個屬性,值爲關聯的另外一方的數據對象列表(門店和所屬訂單是一對多的關係,則門店對象表存在一個訂單列表(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),beanKeyName是訂單用於關聯用戶的字段名稱(例如外鍵「OrderForm.customerID」),beanModelName是用於存放用戶類的字段名稱("例如OrderForm.customer"),dataList是顧客列表(List),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),oneKeyName是門店用於關聯訂單的字段名稱(例如主鍵「Shop.id」),oneModelName是用於存放訂單列表的字段名稱(例如"Shop.orderFomrList"),manyDataList是多方列表(List),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上面 github.com/1399852153/…