博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java 分库关联查询工具类
阅读量:6713 次
发布时间:2019-06-25

本文共 11984 字,大约阅读时间需要 39 分钟。

问题:

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

  举个例子:在查询"订单"(位于订单子系统)列表时,同时需要查询出所关联的"用户"(位于账户子系统)的姓名,而这时由于数据存储在不同的数据源上,没有办法通过一条连表的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
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
beanListToMap(List beanList,String keyName) throws Exception{ //:::创建一个map Map
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
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
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
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://blog.51cto.com/13435991/2323454

你可能感兴趣的文章
剑指 offer (1) -- 数组篇
查看>>
从源码看Spring中IOC容器的实现(二):IOC容器的初始化
查看>>
20181023
查看>>
LeetCode 42 javascript解决方案
查看>>
开发一对一直播系统您需要注意的内容
查看>>
开源|ns4_frame分布式服务框架开发指南
查看>>
用Vue封装Swiper实现图片轮播很简单
查看>>
Android Sensor源码分析总结
查看>>
(基础系列)object clone 的用法、原理和用途
查看>>
图片 文件 转base64
查看>>
Spring Cloud分布式微服务云架构集成项目
查看>>
springmvc整合dubbo
查看>>
函数防抖
查看>>
2月阅读书单及推荐(前端)
查看>>
面试中并发类问题的准备和学习
查看>>
Vuex源码学习(四)module与moduleCollection
查看>>
python基础总结 Part.1
查看>>
【OC梳理】description
查看>>
一篇不太一样的RxJava介绍(二):关于操作符背后的故事
查看>>
FFmpeg模块介绍
查看>>