zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Hihocoder 1275 扫地机器人 计算几何
    CodeForces 771C Bear and Tree Jumps 树形DP
    CodeForces 778D Parquet Re-laying 构造
    CodeForces 785E Anton and Permutation 分块
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale 二分
    Hexo Next 接入 google AdSense 广告
    如何统计 Hexo 网站的访问地区和IP
    Design and Implementation of Global Path Planning System for Unmanned Surface Vehicle among Multiple Task Points
    通过ODBC接口访问人大金仓数据库
  • 原文地址:https://www.cnblogs.com/java-class/p/8143271.html
Copyright © 2011-2022 走看看