java 分库关联查询工具类

237 篇文章 4 订阅
237 篇文章 1 订阅

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

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

欢迎工作一到五年的Java工程师朋友们加入Java架构开发: 854393687
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Java 中实现分库分表有多种方式,这里介绍几种常用的方式。 1. 使用路由算法 路由算法是指通过对数据进行某种计算,得出数据应该存储在哪个数据库、哪个表中的方法。常用的路由算法有: - 哈希路由算法:将数据按照哈希函数计算出的值进行分库分表。 - 范围路由算法:将数据按照某个字段的值范围进行分库分表。 2. 使用中间件 中间件是指作为数据库和应用程序之间的中介,它可以帮助我们实现分库分表。常用的中间件有: - Sharding-JDBC:是一款开源的分库分表中间件,支持 JDBC 协议,可以很方便地在 Java 程序中使用。 - MyCAT:是一款开源的分布式数据库系统,支持分库分表、读写分离、数据同步等功能。 3. 使用代码生成器 代码生成器是指通过配置数据库信息和分库分表规则,自动生成 Java 代码的工具。常用的代码生成器有: - MyBatis Generator:是 MyBatis 官方提供的代码生成器,可以自动生成 MyBatis 的映射文件和 DAO 类。 ### 回答2: 分库分表是一种常见的数据库架构设计,可以提高数据处理和查询的效率,降低数据库的负载压力。下面是使用Java实现分库分表的步骤: 1. 定义数据库分片规则:根据系统的实际需求,确定数据库的分片规则,例如可以按照某个字段的哈希值进行分片,或者按照某个区间范围进行分片等。 2. 创建数据库连接池:使用Java中的连接池技术,如Druid、HikariCP等,创建多个数据库连接池,每个连接池对应一个分片库。 3. 分配数据源:根据分片规则,将数据源和对应的数据库连接池进行关联,以便后续的数据库操作可以根据分片规则选择合适的数据源。 4. 实现数据访问层:创建数据访问层(DAO)的接口和实现类,利用Java的ORM框架如MyBatis或Hibernate,对数据库进行操作。 5. 写入、查询数据:在DAO的实现类中,根据分区规则选择相应的数据源,使用分片规则将数据写入到相应的分片库中。在查询时,根据分区规则选择相应的数据源,查询相应的分片库,然后将结果进行合并。 6. 数据迁移和备份:由于分库分表会产生多个分片库,可能需要进行数据迁移和备份,确保数据的安全和完整性。可以使用Java编写数据迁移和备份的工具。 需要注意的是,分库分表需要考虑数据的一致性和事务处理,对于跨库事务,可以使用Java分布式事务框架如Atomikos、Seata等来处理。此外,还要考虑数据分布不均衡的问题,可以使用一致性哈希算法等解决方案来解决。 ### 回答3: 分库分表是一种数据库分布式架构设计的方法,用于解决单一数据库无法满足大规模数据存储和高并发读写需求的问题。下面是用Java实现分库分表的一般步骤: 1. 数据库选择:选择一款支持分布式数据库的产品,如MySQL Cluster、MongoDB、HBase等。 2. 数据库水平切分:将原始的数据库按照某种规则(如按照用户ID或数据类型)划分成多个子库,每个子库承担部分数据存储的任务。 3. 数据库垂直切分:在每个子库内部,将原始的表按照某种规则(如按照数据类型或数据访问频率)划分成多个分表,每个分表承担部分数据存储的任务。 4. 连接池管理:使用Java连接池管理数据库连接,以提高连接复用率和系统性能。 5. 数据路由:根据分库分表的规则,通过Java代码将数据路由到相应的数据库和表中,实现数据的读写操作。 6. 分布式事务管理:使用Java框架或自行设计分布式事务管理机制,保证分布式数据库的数据一致性。 7. 数据迁移与备份:当需要增加或删除库、表时,通过Java程序实现数据的迁移和备份工作,保证数据的完整性和可用性。 8. 动态扩展:当数据库负载过高时,通过Java代码实现动态扩展,增加库、表和数据库服务器的数量,提升系统性能。 总之,通过合理的分库分表策略和Java编程,可以实现数据库的水平扩展和性能优化,提高系统的并发能力和稳定性。但是需要注意的是,分库分表会增加开发和维护的复杂性,需要仔细权衡利弊。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值