经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
java 分库关联查询工具类
来源:cnblogs  作者:小熊餐馆  时间:2018/11/1 9:42:28  对本文有异议

      问题:

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

  举个例子:在查询"订单"(位于订单子系统)列表时,同时需要查询出所关联的"用户"(位于账户子系统)的姓名,而这时由于数据存储在不同的数据源上,没有办法通过一条连表的sql获取到全部的数据,而是必须进行两次数据库查询,从不同的数据源分别获取数据,并且在web服务器中进行关联映射。在观察了一段时间后,发现进行关联映射的代码大部分都是模板化的,因此产生一个想法,想要把这些模板代码抽象出来,简化开发,也增强代码的可读性。同时,即使在同一个数据源上,如果能将多表联查的需求转化为单表多次查询,也能够减少代码的耦合,同时提高数据库效率。

  设计主要思路:

在关系型数据库中:

  一对一的关系一般表示为:一方的数据表结构中存在一个业务上的外键关联另一张表的主键(订单和用户是一对一的关系,则订单表中存在外键对应于用户表的主键)。

  一对多的关系一般表示为:多方的数据中存在一个业务上的外键关联一方的主键(门店和订单是一对多的关系,则订单表中存在外键对应于门店的主键)。

而在非关系型数据库中:

  一对一的关系一般表示为:一方中存在一个属性,值为关联的另一方的数据对象(订单和用户是一对一的关系,则订单对象中存在一个用户属性)。

  一对多的关系一般表示为:一方中存在一个属性,值为关联的另一方的数据对象列表(门店和所属订单是一对多的关系,则门店对象表存在一个订单列表(List)属性)。

  可以看出java的对象机制,天然就支持非关系型的数据模型,因此大概的思路就是,将查询出来的两个列表进行符合要求的映射即可。

  pojo类:

  1. public class OrderForm {
  2. /**
  3. * 主键id
  4. * */
  5. private String id;
  6. /**
  7. * 所属门店id
  8. * */
  9. private String shopID;
  10. /**
  11. * 关联的顾客id
  12. * */
  13. private String customerID;
  14. /**
  15. * 关联的顾客model
  16. * */
  17. private Customer customer;
  18. }
  19. public class Customer {
  20. /**
  21. * 主键id
  22. * */
  23. private String id;
  24. /**
  25. * 姓名
  26. * */
  27. private String userName;
  28. }
  29. public class Shop {
  30. /**
  31. * 主键id
  32. * */
  33. private String id;
  34. /**
  35. * 门店名
  36. * */
  37. private String shopName;
  38. /**
  39. * 订单列表 (一个门店关联N个订单 一对多)
  40. * */
  41. private List<OrderForm> orderFormList;
  42. }

 

  辅助工具函数:

  1. /***
  2. * 将通过keyName获得对应的bean对象的get方法名称的字符串
  3. * @param keyName 属性名
  4. * @return 返回get方法名称的字符串
  5. */
  6. private static String makeGetMethodName(String keyName){
  7. //:::将第一个字母转为大写
  8. String newKeyName = transFirstCharUpperCase(keyName);
  9. return "get" + newKeyName;
  10. }
  11. /***
  12. * 将通过keyName获得对应的bean对象的set方法名称的字符串
  13. * @param keyName 属性名
  14. * @return 返回set方法名称的字符串
  15. */
  16. private static String makeSetMethodName(String keyName){
  17. //:::将第一个字母转为大写
  18. String newKeyName = transFirstCharUpperCase(keyName);
  19. return "set" + newKeyName;
  20. }
  21. /**
  22. * 将字符串的第一个字母转为大写
  23. * @param str 需要被转变的字符串
  24. * @return 返回转变之后的字符串
  25. */
  26. private static String transFirstCharUpperCase(String str){
  27. return str.replaceFirst(str.substring(0, 1), str.substring(0, 1).toUpperCase());
  28. }
  29. /**
  30. * 判断当前的数据是否需要被转换
  31. *
  32. * 两个列表存在一个为空,则不需要转换
  33. * @return 不需要转换返回 false,需要返回 true
  34. * */
  35. private static boolean needTrans(List beanList,List dataList){
  36. if(listIsEmpty(beanList) || listIsEmpty(dataList)){
  37. return false;
  38. }else{
  39. return true;
  40. }
  41. }
  42. /**
  43. * 列表是否为空
  44. * */
  45. private static boolean listIsEmpty(List list){
  46. if(list == null || list.isEmpty()){
  47. return true;
  48. }else{
  49. return false;
  50. }
  51. }
  52. /**
  53. * 将javaBean组成的list去重 转为map, key为bean中指定的一个属性
  54. *
  55. * @param beanList list 本身
  56. * @param keyName 生成的map中的key
  57. * @return
  58. * @throws Exception
  59. */
  60. public static Map<String,Object> beanListToMap(List beanList,String keyName) throws Exception{
  61. //:::创建一个map
  62. Map<String,Object> map = new HashMap<>();
  63. //:::由keyName获得对应的get方法字符串
  64. String getMethodName = makeGetMethodName(keyName);
  65. //:::遍历beanList
  66. for(Object obj : beanList){
  67. //:::如果当前数据是hashMap类型
  68. if(obj.getClass() == HashMap.class){
  69. Map currentMap = (Map)obj;
  70. //:::使用keyName从map中获得对应的key
  71. String result = (String)currentMap.get(keyName);
  72. //:::放入map中(如果key一样,则会被覆盖去重)
  73. map.put(result,currentMap);
  74. }else{
  75. //:::否则默认是pojo对象
  76. //:::获得get方法
  77. Method getMethod = obj.getClass().getMethod(getMethodName);
  78. //:::通过get方法从bean对象中得到数据key
  79. String result = (String)getMethod.invoke(obj);
  80. //:::放入map中(如果key一样,则会被覆盖去重)
  81. map.put(result,obj);
  82. }
  83. }
  84. //:::返回结果
  85. return map;
  86. }

       

  一对一连接接口定义:

  1. /**
  2. * 一对一连接 : beanKeyName <---> dataKeyName 作为连接条件
  3. *
  4. * @param beanList 需要被存放数据的beanList(主体)
  5. * @param beanKeyName beanList中连接字段key的名字
  6. * @param beanModelName beanList中用来存放匹配到的数据value的属性
  7. * @param dataList 需要被关联的data列表
  8. * @param dataKeyName 需要被关联的data中连接字段key的名字
  9. *
  10. * @throws Exception
  11. */
  12. 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")。

  

  一对一连接代码实现:

  1. /**
  2. * 一对一连接 : beanKeyName <---> dataKeyName 作为连接条件
  3. *
  4. * @param beanList 需要被存放数据的beanList(主体)
  5. * @param beanKeyName beanList中连接字段key的名字
  6. * @param beanModelName beanList中用来存放匹配到的数据value的属性
  7. * @param dataList 需要被关联的data列表
  8. * @param dataKeyName 需要被关联的data中连接字段key的名字
  9. *
  10. * @throws Exception
  11. */
  12. public static void oneToOneLinked(List beanList, String beanKeyName, String beanModelName, List dataList, String dataKeyName) throws Exception {
  13. //:::如果不需要转换,直接返回
  14. if(!needTrans(beanList,dataList)){
  15. return;
  16. }
  17. //:::将被关联的数据列表,以需要连接的字段为key,转换成map,加快查询的速度
  18. Map<String,Object> dataMap = beanListToMap(dataList,dataKeyName);
  19. //:::进行数据匹配连接
  20.    matchedDataToBeanList(beanList,beanKeyName,beanModelName,dataMap);
  21.   }
  22. /**
  23. * 将批量查询出来的数据集合,组装到对应的beanList之中
  24. * @param beanList 需要被存放数据的beanList(主体)
  25. * @param beanKeyName beanList中用来匹配数据的属性
  26. * @param beanModelName beanList中用来存放匹配到的数据的属性
  27. * @param dataMap data结果集以某一字段作为key对应的map
  28. * @throws Exception
  29. */
  30. private static void matchedDataToBeanList(List beanList, String beanKeyName, String beanModelName, Map<String,Object> dataMap) throws Exception {
  31. //:::获得beanList中存放对象的key的get方法名
  32. String beanGetMethodName = makeGetMethodName(beanKeyName);
  33. //:::获得beanList中存放对象的model的set方法名
  34. String beanSetMethodName = makeSetMethodName(beanModelName);
  35. //:::遍历整个beanList
  36. for(Object bean : beanList){
  37. //:::获得bean中key的method对象
  38. Method beanGetMethod = bean.getClass().getMethod(beanGetMethodName);
  39. //:::调用获得当前的key
  40. String currentBeanKey = (String)beanGetMethod.invoke(bean);
  41. //:::从被关联的数据集map中找到匹配的数据
  42. Object matchedData = dataMap.get(currentBeanKey);
  43. //:::如果找到了匹配的对象
  44. if(matchedData != null){
  45. //:::获得bean中对应model的set方法
  46. Class clazz = matchedData.getClass();
  47. //:::如果匹配到的数据是hashMap
  48. if(clazz == HashMap.class){
  49. //:::转为父类map class用来调用set方法
  50. clazz = Map.class;
  51. }
  52. //:::获得主体bean用于存放被关联对象的set方法
  53. Method beanSetMethod = bean.getClass().getMethod(beanSetMethodName,clazz);
  54. //:::执行set方法,将匹配到的数据放入主体数据对应的model属性中
  55. beanSetMethod.invoke(bean,matchedData);
  56. }
  57. }
  58. }

  

  一对多连接接口定义:

  1. /**
  2. * 一对多连接 : oneKeyName <---> manyKeyName 作为连接条件
  3. *
  4. * @param oneDataList '一方' 数据列表
  5. * @param oneKeyName '一方' 连接字段key的名字
  6. * @param oneModelName '一方' 用于存放 '多方'数据的列表属性名
  7. * @param manyDataList '多方' 数据列表
  8. * @param manyKeyName '多方' 连接字段key的名字
  9. *
  10. * 注意: '一方' 存放 '多方'数据的属性oneModelName类型必须为List
  11. *
  12. * @throws Exception
  13. */
  14. 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")。

 

  一对多连接代码实现:

  1. /**
  2. * 一对多连接 : oneKeyName <---> manyKeyName 作为连接条件
  3. *
  4. * @param oneDataList '一方' 数据列表
  5. * @param oneKeyName '一方' 连接字段key的名字
  6. * @param oneModelName '一方' 用于存放 '多方'数据的列表属性名
  7. * @param manyDataList '多方' 数据列表
  8. * @param manyKeyName '多方' 连接字段key的名字
  9. *
  10. * 注意: '一方' 存放 '多方'数据的属性oneModelName类型必须为List
  11. *
  12. * @throws Exception
  13. */
  14. public static void oneToManyLinked(List oneDataList,String oneKeyName,String oneModelName,List manyDataList,String manyKeyName) throws Exception {
  15. if(!needTrans(oneDataList,manyDataList)){
  16. return;
  17. }
  18. //:::将'一方'数据,以连接字段为key,转成map,便于查询
  19. Map<String,Object> oneDataMap = beanListToMap(oneDataList,oneKeyName);
  20. //:::获得'一方'存放 '多方'数据字段的get方法名
  21. String oneDataModelGetMethodName = makeGetMethodName(oneModelName);
  22. //:::获得'一方'存放 '多方'数据字段的set方法名
  23. String oneDataModelSetMethodName = makeSetMethodName(oneModelName);
  24. //:::获得'多方'连接字段的get方法名
  25. String manyDataKeyGetMethodName = makeGetMethodName(manyKeyName);
  26. try {
  27. //:::遍历'多方'列表
  28. for (Object manyDataItem : manyDataList) {
  29. //:::'多方'对象连接key的值
  30. String manyDataItemKey;
  31. //:::判断当前'多方'对象的类型是否是 hashMap
  32. if(manyDataItem.getClass() == HashMap.class){
  33. //:::如果是hashMap类型的,先转为Map对象
  34. Map manyDataItemMap = (Map)manyDataItem;
  35. //:::通过参数key 直接获取对象key连接字段的值
  36. manyDataItemKey = (String)manyDataItemMap.get(manyKeyName);
  37. }else{
  38. //:::如果是普通的pojo对象,则通过反射获得get方法来获取key连接字段的值
  39. //:::获得'多方'数据中key的method对象
  40. Method manyDataKeyGetMethod = manyDataItem.getClass().getMethod(manyDataKeyGetMethodName);
  41. //:::调用'多方'数据的get方法获得当前'多方'数据连接字段key的值
  42. manyDataItemKey = (String) manyDataKeyGetMethod.invoke(manyDataItem);
  43. }
  44. //:::通过'多方'的连接字段key从 '一方' map集合中查找出连接key相同的 '一方'数据对象
  45. Object matchedOneData = oneDataMap.get(manyDataItemKey);
  46. //:::如果匹配到了数据,才进行操作
  47. if(matchedOneData != null){
  48. //:::将当前迭代的 '多方'数据 放入 '一方' 的对应的列表中
  49. setManyDataToOne(matchedOneData,manyDataItem,oneDataModelGetMethodName,oneDataModelSetMethodName);
  50. }
  51. }
  52. }catch(Exception e){
  53. throw new Exception(e);
  54. }
  55. }
  56. /**
  57. * 将 '多方' 数据存入 '一方' 列表中
  58. * @param oneData 匹配到的'一方'数据
  59. * @param manyDataItem 当前迭代的 '多方数据'
  60. * @param oneDataModelGetMethodName 一方列表的get方法名
  61. * @param oneDataModelSetMethodName 一方列表的set方法名
  62. * @throws Exception
  63. */
  64. private static void setManyDataToOne(Object oneData,Object manyDataItem,String oneDataModelGetMethodName,String oneDataModelSetMethodName) throws Exception {
  65. //:::获得 '一方' 数据中存放'多方'数据属性的get方法
  66. Method oneDataModelGetMethod = oneData.getClass().getMethod(oneDataModelGetMethodName);
  67. //::: '一方' 数据中存放'多方'数据属性的set方法
  68. Method oneDataModelSetMethod;
  69. try {
  70. //::: '一方' set方法对象
  71. oneDataModelSetMethod = oneData.getClass().getMethod(oneDataModelSetMethodName,List.class);
  72. }catch(NoSuchMethodException e){
  73. throw new Exception("未找到满足条件的'一方'set方法");
  74. }
  75. //:::获得存放'多方'数据get方法返回值类型
  76. Class modelType = oneDataModelGetMethod.getReturnType();
  77. //::: get方法返回值必须是List
  78. if(modelType.equals(List.class)){
  79. //:::调用get方法,获得数据列表
  80. List modelList = (List)oneDataModelGetMethod.invoke(oneData);
  81. //:::如果当前成员变量为null
  82. if(modelList == null){
  83. //:::创建一个新的List
  84. List newList = new ArrayList<>();
  85. //:::将当前的'多方'数据存入list
  86. newList.add(manyDataItem);
  87. //:::将这个新创建出的List赋值给 '一方'的对象
  88. oneDataModelSetMethod.invoke(oneData,newList);
  89. }else{
  90. //:::如果已经存在了List
  91. //:::直接将'多方'数据存入list
  92. modelList.add(manyDataItem);
  93. }
  94. }else{
  95. throw new Exception("一对多连接时,一方指定的model对象必须是list类型");
  96. }
  97. }

  测试用例在我的github上面 https://github.com/1399852153/linkedQueryUtil。

 

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号