java 分库关联查询工具类

## 问题:

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 问题: 由于公司业务扩大,各个子系统陆续迁移和部署在不同的数据源上,这样方便扩容,但是因此引出了一些问题。 举个例...
    java菜阅读 196评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 没有绝对的权利,制度也也没有绝对的好坏。这个世界是丰富多彩多样化的,因此,理解和接受这个世界,理解和接受自己,同时...
    浪客诗心阅读 177评论 0 0
  • 春 种植一个 希望 用辛劳做肥料 指望着收获 茂盛 夏 感恩一片绿荫 带来的凉爽 或许 ...
    雕心阁主人阅读 392评论 1 0