问题:

  由于公司业务扩大,各个子系统陆续迁移和部署在不同的数据源上,这样方便扩容,但是因此引出了一些问题。

  举个例子:在查询"订单"(位于订单子系统)列表时,同时需要查询出所关联的"用户"(位于账户子系统)的姓名,而这时由于数据存储在不同的数据源上,没有办法通过一条连表的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

  这是我的第一篇技术博客,无论是排版还是分享的内容上面都还有很多的不足之处,希望大家指出,互相交流,互相进步。

java 分库关联查询工具类的更多相关文章

  1. 最全的Java操作Redis的工具类,使用StringRedisTemplate实现,封装了对Redis五种基本类型的各种操作!

    转载自:https://github.com/whvcse/RedisUtil 代码 ProtoStuffSerializerUtil.java import java.io.ByteArrayInp ...

  2. Java并发多线程 - 并发工具类JUC

    安全共享对象策略 1.线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改 2.共享只读 : 一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问, 但是任何线程都 ...

  3. Redis 工具类 java 实现的redis 工具类

    最近了解了一下非关系型数据库 redis 会使用简单的命令 在自己本地电脑 使用时必须先启动服务器端 在启动客户端 redis 简介 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内 ...

  4. Java线程的并发工具类

    Java线程的并发工具类. 一.fork/join 1. Fork-Join原理 在必要的情况下,将一个大任务,拆分(fork)成若干个小任务,然后再将一个个小任务的结果进行汇总(join). 适用场 ...

  5. 分享自研实现的多数据源(支持同DB不同表、跨DB表、内存数据、外部系统数据等)分页查询工具类实现原理及使用

    思考: 提起分页查询,想必任何一个开发人员(不论是新手还是老手)都能快速编码实现,实现原理再简单不过,无非就是写一条SELECT查询的SQL语句,ORDER BY分页排序的字段, 再结合limit ( ...

  6. Rhino+envjs-1.2.js 在java运行网站js 工具类

    java爬虫遇到个页面加密的东西,找了些资料学习学习 做了个java运行js的工具类,希望对大家有用,其中用到client(获取js)可以自行换成自己的client.主要是用了 Rhino就是Java ...

  7. java中常用的工具类(一)

    我们java程序员在开发项目的是常常会用到一些工具类.今天我汇总了一下java中常用的工具方法.大家可以在项目中使用.可以收藏!加入IT江湖官方群:383126909 我们一起成长 一.String工 ...

  8. Java学习-041-颜色工具类(RGB,HEX)

    在日常的网页开发中,经常需要进行颜色数值获取.转换,例如获取红色,获取蓝色,获取绿色,RGB转十六进制颜色,十六进制颜色转RGB等,因而在学习过程中,写了一个小工具类,仅供各位小主参考! 多不闲言,直 ...

  9. JAVA中封装JSONUtils工具类及使用

    在JAVA中用json-lib-2.3-jdk15.jar包中提供了JSONObject和JSONArray基类,用于JSON的序列化和反序列化的操作.但是我们更习惯将其进一步封装,达到更好的重用. ...

随机推荐

  1. java基础-day18

    第07天 集合 今日内容介绍 u  HashSet集合 u  HashMap集合 第1章   HashSet集合 1.1  Set接口的特点 Set体系的集合: A:存入集合的顺序和取出集合的顺序不一 ...

  2. 转载:Java项目读取配置文件时,FileNotFoundException 系统找不到指定的文件,System.getProperty("user.dir")的理解

    唉,读取个文件,也就是在项目里面去获得配置文件的目录,然后,变成文件,有事没事,总是出个 FileNotFoundException  系统找不到指定的文件,气死人啦. 还有就是:System.get ...

  3. ES基本操作

    倒排索引 ElasticSearch使用一种称为倒排索引的结构,它适用于快速的全文搜索.一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表. 查询 # 查看索引配置GE ...

  4. stm32f103_高级定时器——输入捕获/输出比较中断+pwm=spwm生成

    ****************************首选我们了解一下它们的功能吧********************************************************** ...

  5. 4.css基础

    1 Css概念 CSS 指层叠样式表 (Cascading Style Sheets)(级联样式表) Css是用来美化html标签的,相当于页面化妆. ◆样式表书写位置 2选择器 2.1 写法 选择器 ...

  6. Android-Kotlin-空值处理&字符串比较&常量

    空值处理: [案例一:] 1.Kotlin对控制处理很严格,默认就不能写null,否则编译都不通过: 描述Car汽车对象: package cn.kotlin.kotlin_base01 /** * ...

  7. Android-Retrofit-2.0-Post与Get-请求有道词典翻译

    Retrofit-2.0版本后,内置已经集成了OKHttp,在使用Retrofit的时候 看似是Retrofit去网络请求的 实际上Retrofit只是封装,所以不要以为Retrofit是网络请求框架 ...

  8. java 堆排序的实现

    堆就是一个完全二叉树,堆要求是指 该节点大于它的两个子节点.而两个字节点大小不一定. 堆排序的最坏时间复杂度为nlog(n),平均也为nlog(n),占用空间为o(1),是一种比较排序算法. 堆排序也 ...

  9. 如何获取 docker 容器(container)的 ip 地址

    1. 进入容器内部后 cat /etc/hosts 会显示自己以及(– link)软连接的容器IP 2.使用命令 docker inspect --format '{{ .NetworkSetting ...

  10. net 把指定 URI 的资源下载到本地

    DirectoryInfo dir = new DirectoryInfo(AppContext.BaseDirectory); var path = dir.FullName + @"te ...