• Hibernate 离线对象构建通用查询


    1.业务场景

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

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

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

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

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

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

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

    2.技术细节

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

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

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

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

      主系统服务核心代码:

    @Component
    @WebService
    @SOAPBinding(style = SOAPBinding.Style.RPC)
    public class CommonQueryWsImpl implements CommonQueryWs {
    
        @Resource
        private SessionFactory sessionFactory;
    
        @Override
        @Transactional(readOnly = true)
        public String getPo(String poName, List<String> andList) {
            DetachedCriteria detachedCriteria = getDetachedCriteria(poName, andList);
    
            Object result = detachedCriteria.getExecutableCriteria(sessionFactory.getCurrentSession()).setMaxResults(1).uniqueResult();
            return ObjectUtil.isNull(result) ? "{}" : JSONUtil.toJsonStr(result);
        }
    
        @Override
        @Transactional(readOnly = true)
        public String listPo(String poName, List<String> andList) {
            DetachedCriteria detachedCriteria = getDetachedCriteria(poName, andList);
    
            List list = detachedCriteria.getExecutableCriteria(sessionFactory.getCurrentSession()).list();
            return JSONUtil.toJsonStr(list);
        }
    
        /**
         * 获取 hiber 离线查询对象
         *
         * @param poName  biber 实体对象
         * @param andList andSql 列表
         * @return 离线查询对象
         */
        private DetachedCriteria getDetachedCriteria(String poName, List<String> andList) {
            DetachedCriteria detachedCriteria = DetachedCriteria.forEntityName(poName);
    
            String[] split;
            for (String andStr : andList) {
                split = Base64.decodeStr(andStr).split("#");
                String andSql = split[0];
                String andParam = split[1];
    
                if (andParam.contains(",")) {
                    String[] andParams = andParam.split(",");
                    Type[] types = new Type[andParams.length];
                    for (int i = 0; i < types.length; i++) {
                        types[i] = StringType.INSTANCE;
                    }
    
                    detachedCriteria.add(Restrictions.sqlRestriction(andSql, andParams, types));
                } else {
                    detachedCriteria.add(Restrictions.sqlRestriction(andSql, andParam, StringType.INSTANCE));
                }
            }
            return detachedCriteria;
        }
    }
    View Code

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

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

        <!--激活启用基于 @Transactional 的声明式事务管理方式-->
        <tx:annotation-driven/>
    
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/db_ids?useUnicode=true&amp;characterEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="111111"/>
        </bean>
    
        <!-- 配置hibernate session工厂,需添加 spring-orm -->
        <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="hibernateProperties">
                <props>
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.format_sql">true</prop>
                </props>
            </property>
    
            <!-- 自动扫描注解方式配置的hibernate类文件 -->
            <property name="packagesToScan">
                <list>
                    <value>com.rambo.**.po</value>
                </list>
            </property>
        </bean>
    
        <!-- 配置事务管理器 -->
        <bean name="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
            <property name="sessionFactory" ref="sessionFactory"/>
        </bean>
    View Code

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

        <listener>
            <description>上下文监听</description>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath:spring-cxf-context.xml
                classpath:spring-hiber-context.xml
            </param-value>
        </context-param>
    
        <servlet>
            <description>Apache CXF WebService 服务</description>
            <servlet-name>CXF</servlet-name>
            <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>CXF</servlet-name>
            <url-pattern>/ws/*</url-pattern>
        </servlet-mapping>
    View Code

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

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

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

    3.调用姿势

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

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

        @Override
        public <T> T getPo(Class<T> t, Map<String, String> andParamMap) {
            try {
                String retStr = recivePo(t, andParamMap, "get");
                return JSONUtil.toBean(retStr, t);
            } catch (Exception e) {
                logger.error("获取单个通用查询业务数据实体表时发生错误:{}", e.toString());
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        public <T> List<T> listPo(Class<T> t, Map<String, String> andParamMap) {
            try {
                JSONArray poJa = new JSONArray(recivePo(t, andParamMap, "list"));
                logger.info("获取多个通用查询业务数据实体表 json 对象数量:{}", poJa.size());
    
                poJa.removeIf(next -> ObjectUtil.isNull(next) || "null".equalsIgnoreCase(next.toString()));
                logger.info("进行过滤数据后 json 对象数量:{}", poJa.size());
                return poJa.toList(t);
            } catch (Exception e) {
                logger.error("获取多个通用查询业务数据实体表时发生错误:{}", e.toString());
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 调用远程 ws 服务获取通用数据
         *
         * @param t           要获取的实体 po
         * @param andParamMap 参数 map
         * @return 通用数据 json
         */
        private String recivePo(Class t, Map<String, String> andParamMap, String method) throws Exception {
            Assert.notNull(t);
            Assert.notNull(andParamMap);
    
            String mappingUrl = "http://localhost:4042/hiber-common-query-server/ws/CommonQueryWs?wsdl";
            String po;
            if ("get".equals(method)) {
                po = getService(mappingUrl).getPo(t.getName(), handleParamList(andParamMap));
            } else {
                po = getService(mappingUrl).listPo(t.getName(), handleParamList(andParamMap));
            }
    
            Assert.notEmpty(po);
            logger.info("调用地址:{},获取表:{}", mappingUrl, t.getName());
            logger.info("返回的对象 json:{}", po);
            return po;
        }
    View Code

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

        @Test
        public void testGetCommQueryPo() throws Exception {
            CommonQueryServiceImpl commonQueryService = new CommonQueryServiceImpl();
    
            SysUserPO sysUserPO = commonQueryService.getPo(SysUserPO.class, MapUtil.of("uuid = ?", "966364ac90e74fba8340a3aa9992ced1"));
            Assert.notNull(sysUserPO);
        }
    
        @Test
        public void testGetCommQueryPo2() throws Exception {
            CommonQueryServiceImpl commonQueryService = new CommonQueryServiceImpl();
            HashMap<String, String> andParamMap = new LinkedHashMap<>();
            andParamMap.put("(phone = ? or email = ?)", "18745962314,riceshop@live.cn");
    
            List<SysUserPO> sysUserPOList = commonQueryService.listPo(SysUserPO.class, andParamMap);
            Assert.notNull(sysUserPOList);
        }
    
        /**
         * Method: listPo(Class<T> t, Map<String, String> andParamMap)
         */
        @Test
        public void testListCommQuery() throws Exception {
            CommonQueryServiceImpl commonQueryService = new CommonQueryServiceImpl();
    
            HashMap<String, String> andParamMap = new LinkedHashMap<>();
            andParamMap.put("name like ? ", "%黄%");
            andParamMap.put("date_format(create_date,'%Y%m%d') >= ? ", "201809");
            andParamMap.put("photo = ? order by update_date desc", "1");
    
            List<SysUserPO> sysUserPOList = commonQueryService.listPo(SysUserPO.class, andParamMap);
            Assert.notNull(sysUserPOList);
        }
    View Code

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

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

  • 相关阅读:
    用C#实现宽带重新拨号
    CALLBACK FUNCTION 回调函数
    编译程序 VS 解释程序
    《围城》读书笔记
    鼠标点击器
    工作与找工作的日子
    Windows 7下VS2003的查找无响应问题
    收藏几句关于程序的名言
    static知识小结
    如何定义和实现一个类的成员函数为回调函数(转)
  • 原文地址:https://www.cnblogs.com/java-class/p/8143271.html
Copyright © 2020-2023  润新知