Hibernate 離線對象構建通用查詢

1.業務場景

   當下主系統衍生子業務系統已經成爲常態,像京東的物流和金融,阿里的支付寶和淘寶。mysql

   子業務系統須要對主系統的資源進行訪問,這裏的資源我具體化爲數據庫數據,但平常業務中可能不僅是數據。git

   抽象服務給子業務系統進行調用訪問? 各類各樣子業務系統可能會出現千奇百怪的需求,主系統只有不斷增長服務去擁抱變化。web

   建立多個數據庫子帳號給子業務系統?沒法統一監控,並且很容易致使數據庫瓶頸,到時候就須要加服務器資源。spring

   權衡利弊和本身技術棧以後開始實踐之路,大致目標:sql

   a.通用方式獲取主系統數據,且新增需求時主系統訪問方式依然不變,在對應的子業務系統進行數據的處理;數據庫

   b.進行統一監控,把控對外數據,收集日誌並進行數據分析和挖掘;apache

2.技術細節

  核心爲 Hibernate DetachedCriteria 離線查詢對象,對於 DetachedCriteria 我這裏就不說了請自行百度json

  當下 JavaEE 服務方式有不少,restful/soap/spring http invoker 等等,看你項目具體的服務框,這裏我不贅述。api

  我將自身項目中抽取中該場景關鍵代碼,剔除繁瑣的業務代碼,展現 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

相關文章
相關標籤/搜索