1.业务场景

当下主系统衍生子业务系统已经成为常态,像京东的物流和金融,阿里的支付宝和淘宝。

子业务系统需要对主系统的资源进行访问,这里的资源我具体化为数据库数据,但日常业务中可能不只是数据。

抽象服务给子业务系统进行调用访问? 各种各样子业务系统可能会出现千奇百怪的需求,主系统只有不断增加服务去拥抱变化。

创建多个数据库子账号给子业务系统?无法统一监控,而且很容易导致数据库瓶颈,到时候就需要加服务器资源。

权衡利弊和自己技术栈之后开始实践之路,大体目标:

a.通用方式获取主系统数据,且新增需求时主系统访问方式依然不变,在对应的子业务系统进行数据的处理;

b.进行统一监控,把控对外数据,收集日志并进行数据分析和挖掘;

2.技术细节

核心为 Hibernate DetachedCriteria 离线查询对象,对于 DetachedCriteria 我这里就不说了请自行百度

当下 JavaEE 服务方式有很多,restful/soap/spring http invoker 等等,看你项目具体的服务框,这里我不赘述。

我将自身项目中抽取中该场景关键代码,剔除繁琐的业务代码,展示 hiber 通用查询带来的便利性和可扩展性。

实例中服务采用 cxf webservice,spring 做为托管容器,Base64 编码查询条件进行参数的传递。

主系统服务核心代码:

  1. @Component
  2. @WebService
  3. @SOAPBinding(style = SOAPBinding.Style.RPC)
  4. public class CommonQueryWsImpl implements CommonQueryWs {
  5.  
  6. @Resource
  7. private SessionFactory sessionFactory;
  8.  
  9. @Override
  10. @Transactional(readOnly = true)
  11. public String getPo(String poName, List<String> andList) {
  12. DetachedCriteria detachedCriteria = getDetachedCriteria(poName, andList);
  13.  
  14. Object result = detachedCriteria.getExecutableCriteria(sessionFactory.getCurrentSession()).setMaxResults(1).uniqueResult();
  15. return ObjectUtil.isNull(result) ? "{}" : JSONUtil.toJsonStr(result);
  16. }
  17.  
  18. @Override
  19. @Transactional(readOnly = true)
  20. public String listPo(String poName, List<String> andList) {
  21. DetachedCriteria detachedCriteria = getDetachedCriteria(poName, andList);
  22.  
  23. List list = detachedCriteria.getExecutableCriteria(sessionFactory.getCurrentSession()).list();
  24. return JSONUtil.toJsonStr(list);
  25. }
  26.  
  27. /**
  28. * 获取 hiber 离线查询对象
  29. *
  30. * @param poName biber 实体对象
  31. * @param andList andSql 列表
  32. * @return 离线查询对象
  33. */
  34. private DetachedCriteria getDetachedCriteria(String poName, List<String> andList) {
  35. DetachedCriteria detachedCriteria = DetachedCriteria.forEntityName(poName);
  36.  
  37. String[] split;
  38. for (String andStr : andList) {
  39. split = Base64.decodeStr(andStr).split("#");
  40. String andSql = split[0];
  41. String andParam = split[1];
  42.  
  43. if (andParam.contains(",")) {
  44. String[] andParams = andParam.split(",");
  45. Type[] types = new Type[andParams.length];
  46. for (int i = 0; i < types.length; i++) {
  47. types[i] = StringType.INSTANCE;
  48. }
  49.  
  50. detachedCriteria.add(Restrictions.sqlRestriction(andSql, andParams, types));
  51. } else {
  52. detachedCriteria.add(Restrictions.sqlRestriction(andSql, andParam, StringType.INSTANCE));
  53. }
  54. }
  55. return detachedCriteria;
  56. }
  57. }

主系统服务端会用 spring 托管 hiber 所需实体类,配置基于注解的声明式事务管理。

还有种基于正则方法名进行拦截的事务管理,底层都是 AOP 方式,在拦截前后进行事务分配和回滚。

  1. <!--激活启用基于 @Transactional 的声明式事务管理方式-->
  2. <tx:annotation-driven/>
  3.  
  4. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  5. <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  6. <property name="url" value="jdbc:mysql://localhost:3306/db_ids?useUnicode=true&amp;characterEncoding=utf8"/>
  7. <property name="username" value="root"/>
  8. <property name="password" value="111111"/>
  9. </bean>
  10.  
  11. <!-- 配置hibernate session工厂,需添加 spring-orm -->
  12. <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
  13. <property name="dataSource" ref="dataSource"/>
  14. <property name="hibernateProperties">
  15. <props>
  16. <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
  17. <prop key="hibernate.show_sql">true</prop>
  18. <prop key="hibernate.format_sql">true</prop>
  19. </props>
  20. </property>
  21.  
  22. <!-- 自动扫描注解方式配置的hibernate类文件 -->
  23. <property name="packagesToScan">
  24. <list>
  25. <value>com.rambo.**.po</value>
  26. </list>
  27. </property>
  28. </bean>
  29.  
  30. <!-- 配置事务管理器 -->
  31. <bean name="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
  32. <property name="sessionFactory" ref="sessionFactory"/>
  33. </bean>

最后在服务端 web.xml 中先启动 spring 容器后初始化 cxf 服务。

  1. <listener>
  2. <description>上下文监听</description>
  3. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4. </listener>
  5.  
  6. <context-param>
  7. <param-name>contextConfigLocation</param-name>
  8. <param-value>
  9. classpath:spring-cxf-context.xml
  10. classpath:spring-hiber-context.xml
  11. </param-value>
  12. </context-param>
  13.  
  14. <servlet>
  15. <description>Apache CXF WebService 服务</description>
  16. <servlet-name>CXF</servlet-name>
  17. <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
  18. </servlet>
  19. <servlet-mapping>
  20. <servlet-name>CXF</servlet-name>
  21. <url-pattern>/ws/*</url-pattern>
  22. </servlet-mapping>

到此,服务端核心部分代码和配置都已结束,其中有很多可变点。

a. 数据库连接我实例中使用了简单的 jdbc 的方式,当然你可以使用连接池像 dbcp/c3p0/druid等等;

b. 数据库配置作为可变配置,实例中硬编码到了 xml 当中,当然是不可取的行为,可以抽取出来到配置文件中;

3.调用姿势

服务抽象完且可以正常启动,就可以进行服务的调用,实例中我使用了 cxf 作为服务的表现形式(可以生成客户端代码或直接在 spring 中配置)。

如果你自身项目中在使用其他服务方式,那么你也很清楚服务的调用方式,正确访问服务后还需要对服务进行封装给子业务系统使用。

  1. @Override
  2. public <T> T getPo(Class<T> t, Map<String, String> andParamMap) {
  3. try {
  4. String retStr = recivePo(t, andParamMap, "get");
  5. return JSONUtil.toBean(retStr, t);
  6. } catch (Exception e) {
  7. logger.error("获取单个通用查询业务数据实体表时发生错误:{}", e.toString());
  8. e.printStackTrace();
  9. }
  10. return null;
  11. }
  12.  
  13. @Override
  14. public <T> List<T> listPo(Class<T> t, Map<String, String> andParamMap) {
  15. try {
  16. JSONArray poJa = new JSONArray(recivePo(t, andParamMap, "list"));
  17. logger.info("获取多个通用查询业务数据实体表 json 对象数量:{}", poJa.size());
  18.  
  19. poJa.removeIf(next -> ObjectUtil.isNull(next) || "null".equalsIgnoreCase(next.toString()));
  20. logger.info("进行过滤数据后 json 对象数量:{}", poJa.size());
  21. return poJa.toList(t);
  22. } catch (Exception e) {
  23. logger.error("获取多个通用查询业务数据实体表时发生错误:{}", e.toString());
  24. e.printStackTrace();
  25. }
  26. return null;
  27. }
  28.  
  29. /**
  30. * 调用远程 ws 服务获取通用数据
  31. *
  32. * @param t 要获取的实体 po
  33. * @param andParamMap 参数 map
  34. * @return 通用数据 json
  35. */
  36. private String recivePo(Class t, Map<String, String> andParamMap, String method) throws Exception {
  37. Assert.notNull(t);
  38. Assert.notNull(andParamMap);
  39.  
  40. String mappingUrl = "http://localhost:4042/hiber-common-query-server/ws/CommonQueryWs?wsdl";
  41. String po;
  42. if ("get".equals(method)) {
  43. po = getService(mappingUrl).getPo(t.getName(), handleParamList(andParamMap));
  44. } else {
  45. po = getService(mappingUrl).listPo(t.getName(), handleParamList(andParamMap));
  46. }
  47.  
  48. Assert.notEmpty(po);
  49. logger.info("调用地址:{},获取表:{}", mappingUrl, t.getName());
  50. logger.info("返回的对象 json:{}", po);
  51. return po;
  52. }

子业务系统可独立依赖 api jar, 使用 spring 托管服务进行注入或者也可以简单粗暴的 new 一个进行调用。

  1. @Test
  2. public void testGetCommQueryPo() throws Exception {
  3. CommonQueryServiceImpl commonQueryService = new CommonQueryServiceImpl();
  4.  
  5. SysUserPO sysUserPO = commonQueryService.getPo(SysUserPO.class, MapUtil.of("uuid = ?", "966364ac90e74fba8340a3aa9992ced1"));
  6. Assert.notNull(sysUserPO);
  7. }
  8.  
  9. @Test
  10. public void testGetCommQueryPo2() throws Exception {
  11. CommonQueryServiceImpl commonQueryService = new CommonQueryServiceImpl();
  12. HashMap<String, String> andParamMap = new LinkedHashMap<>();
  13. andParamMap.put("(phone = ? or email = ?)", "18745962314,riceshop@live.cn");
  14.  
  15. List<SysUserPO> sysUserPOList = commonQueryService.listPo(SysUserPO.class, andParamMap);
  16. Assert.notNull(sysUserPOList);
  17. }
  18.  
  19. /**
  20. * Method: listPo(Class<T> t, Map<String, String> andParamMap)
  21. */
  22. @Test
  23. public void testListCommQuery() throws Exception {
  24. CommonQueryServiceImpl commonQueryService = new CommonQueryServiceImpl();
  25.  
  26. HashMap<String, String> andParamMap = new LinkedHashMap<>();
  27. andParamMap.put("name like ? ", "%黄%");
  28. andParamMap.put("date_format(create_date,'%Y%m%d') >= ? ", "201809");
  29. andParamMap.put("photo = ? order by update_date desc", "1");
  30.  
  31. List<SysUserPO> sysUserPOList = commonQueryService.listPo(SysUserPO.class, andParamMap);
  32. Assert.notNull(sysUserPOList);
  33. }

项目已托管到 git 上有兴趣的可以检下来本地跑一跑,这次仅做抛砖引玉只用,如对你有帮助有启用,别忘记点赞。

https://gitee.com/LanboEx/hiber-common-query

Hibernate 离线对象构建通用查询的更多相关文章

  1. hibernate离线查询DetachedCriteria清除上次的查询条件

    1 原例概述 别名重复问题之后,我们还需要解决的问题就是: 如何清除hibernate的上次查询条件,如果不清除,将会导致上次的查询条件和下次的查询条件合并到了一起. 上次的查询条件和本次的查询条件合 ...

  2. 1.1Hibernate持久化类和Hibernate持久化对象状态

    一.持久化对象po类 1.po定义 PO,是Persistent Object的缩写,是持久化类.PO是由PO=POJO+hbm映射配置组成. 2.通俗理解 PO类即持久化类,其实就是一个普通的Jav ...

  3. hibernate 持久化对象的三个状态

    Hibernate中的对象有3种状态 瞬时对象(TransientObjects).持久化对象(PersistentObjects)和离线对象(DetachedObjects也叫做脱管对象) Tran ...

  4. Hibernate核心对象

    1.Configuration Configuration 类负责管理Hibernate的配置信息.它包括如下内容: Hibernate运行的底层信息:数据库的URL.用户名.密码.JDBC驱动类,数 ...

  5. Hibernate之对象的三种状态

    Hibernate之Java对象的三种状态 一.简述 本博文大部分的思想和内容引子CSND上名为 FG2006 这位大神的文章,连接地址为:http://blog.csdn.net/fg2006/ar ...

  6. 攻城狮在路上(壹) Hibernate(六)--- 通过Hibernate操纵对象(上)

    一.Hibernate缓存简介: Session接口是Hibernate向应用程序提供的操纵数据接口的最主要接口,它提供了基本的保存.更新.删除和加载Java对象的方法. Session具有一个缓存, ...

  7. 使用spring+hibernate+atomikos+tomcat构建分布式事务

    本文通过一个demo,介绍如何使用spring+hibernate+atomikos+tomcat构建在一个事务中涉及两个数据源的web应用. demo功能:实现一个能成功提交和回滚的涉及两个数据库数 ...

  8. Hibernate中对象的3种状态:瞬时态、持久态、脱管态

    Hibernate的对象有3种状态,分别为:瞬时态(Transient). 持久态(Persistent).脱管态(Detached).处于持久 态的对象也称为PO(Persistence Objec ...

  9. Hibernate实体对象三种状态

    Hibernate实体对象生命周期: 1. 自由状态(Transient,临时状态,瞬态) 在内存中自由存在,与数据库无关,未被Hibernate的Session管理 2. 持久状态(Persiste ...

随机推荐

  1. 数仓1.4 |业务数仓搭建| 拉链表| Presto

    电商业务及数据结构 SKU库存量,剩余多少SPU商品聚集的最小单位,,,这类商品的抽象,提取公共的内容 订单表:周期性状态变化(order_info) id 订单编号 total_amount 订单金 ...

  2. 给定数组长度2n,分成n对,求n对最小元素之和最大

    给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大. 示例 ...

  3. 1e9个兵临城下

    https://ac.nowcoder.com/acm/contest/321#question 代码写得蛮丑的..真的很丑 像是高中教的veen图,并涉及到容斥原理. #include<cst ...

  4. Activity插件化解决方案

    --摘自<android插件化开发指南> 1.宿主App加载插件中的类 2.最简单的插件化方案就是在宿主的androidmanifest.xml中申明插件中的四大组件 把插件dex合并到宿 ...

  5. Manjaro (KDE)安装踩坑记录

    1.如果双显卡无法安装系统可以进如BIOS屏蔽显卡后进入安装 2.如果安装kde版本后容易冻屏.死机,可以尝试安装闭源驱动 3.如果出现resolving time out 10000ms 这样的问题 ...

  6. HDU 3415 Max Sum of Max-K-sub-sequence【单调队列】

    <题目链接> 题目大意: 给你一段从1~N的圆形序列,要你求出这段圆形序列中长度不超过K的最大连续子序列之和是多少,并且输出这子序列的起点和终点. 解题分析: 既然是求连续子序列之和,我们 ...

  7. Windows下更改MySQL 数据库文件存放位置

    更改默认的mysql数据库目录 将 C:\Documents and Settings\All Users\Application Data\MySQL\MySQL Server 5.1\data 改 ...

  8. 潭州课堂25班:Ph201805201 django 项目 第二十三课 文章主页 轮播图前端实现 热门新闻推荐实现 详情页实现 (课堂笔记)

    前台代码 // 在static/js/news/index.js文件中 $(function () { // 新闻列表功能 let $newsLi = $(".news-nav ul li& ...

  9. python系统编程(四)

    进程池Pool 当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到mu ...

  10. 【NOI2015】【BZOJ4196】软件包管理器 - 题解

    Description Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖( ...