當下主系統衍生子業務系統已經成爲常態,像京東的物流和金融,阿里的支付寶和淘寶。mysql
子業務系統須要對主系統的資源進行訪問,這裏的資源我具體化爲數據庫數據,但平常業務中可能不僅是數據。git
抽象服務給子業務系統進行調用訪問? 各類各樣子業務系統可能會出現千奇百怪的需求,主系統只有不斷增長服務去擁抱變化。web
建立多個數據庫子帳號給子業務系統?沒法統一監控,並且很容易致使數據庫瓶頸,到時候就須要加服務器資源。spring
權衡利弊和本身技術棧以後開始實踐之路,大致目標:sql
a.通用方式獲取主系統數據,且新增需求時主系統訪問方式依然不變,在對應的子業務系統進行數據的處理;數據庫
b.進行統一監控,把控對外數據,收集日誌並進行數據分析和挖掘;apache
核心爲 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; } }
主系統服務端會用 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&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>
最後在服務端 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>
到此,服務端核心部分代碼和配置都已結束,其中有不少可變點。
a. 數據庫鏈接我實例中使用了簡單的 jdbc 的方式,固然你可使用鏈接池像 dbcp/c3p0/druid等等;
b. 數據庫配置做爲可變配置,實例中硬編碼到了 xml 當中,固然是不可取的行爲,能夠抽取出來到配置文件中;
服務抽象完且能夠正常啓動,就能夠進行服務的調用,實例中我使用了 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; }
子業務系統可獨立依賴 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); }
項目已託管到 git 上有興趣的能夠檢下來本地跑一跑,此次僅作拋磚引玉只用,如對你有幫助有啓用,別忘記點贊。