olap分析平臺的設計與實現(十二)- 從後端模型到前端表格模型(上)

本節的要點是:

  1. 根據模型動態生成mdx語句

打開表單進行數據展示的邏輯:

     判斷分析表單是否包含所有維度

          是否是大數據表單

            如果不是大數據表單,就全表打開。

                查找業務規則、在界面上加載業務規則    runBusinessRule(formId,loadType,isFullMode,hasRule);

                在界面上加載表單。OlapManagement?method=olapShowData&formId="+formId

                 是否需要全新生成,(完全沒有打開的表要全新生成)

                查找【期間】可分攤維度在行或列上,(如果不在行列上使用行上第一個維度進行分攤)

                是否是隻讀表單

              生成formGrid:(這個方法,可以理解成由formshadow 轉換爲 formGrid的方法,由後端模型轉換爲前端模型)

             queryFormGridByFormId(form, isFullMode, formShadow, userId,pageMembers);

              調用showData:()

	...
	//根據formid 獲取CForm對象
	CForm form = getBgFormById(formId);
	...
	/**根據form對象,構建FormShadow  ifFilter--是否根據權限過濾數據
	FormShadows表單映射對象,formShadow可以看成前端模型
	**/
	FormShadow formShadow = new FormShadow(form, userid, ifFilter);
	
	//檢查過濾後是否擁有可見維度成員(行、列、頁面、視點),如果有,才能走一步
	boolean ifHasMember = checkHasMembersOnRowCOl(formShadow.getColMembers());
	boolean ifHasMember2 = checkHasMembersOnRowCOl(formShadow.getRowMembers());
	checkHasMembersOnPageView(formShadow.getPageMembers(),pageMemberList)  //檢查頁面上是否有成員
	checkHasMembersOnPageView(formShadow.getViewMembers(), viewMemberList   檢查視點上是否有成員
		//獲取前端的formgrid
		FormGrid formGrid0 = (FormGrid) request.getAttribute("formGrid");
....
//構建formGrid 
formGrid = queryFormGridByFormId(form,isFullMode, formShadow, userId,pageMembers);
		
	......
showData(defaultAliasTable,showMap, html, formGrid, formShadow,request.getContextPath(),formReadyOnly);
...	
		

queryFormGridByFormId接口

	/**
	 * @param showAll (true - 以全部下鑽方式打開分析表單    false - 以全部上卷方式打開成本表)
	 * 通過 ID查詢分析表單,返回FormGrid類實例
	 * @param pageLayout 頁面軸上選定的維度成員 --批量導出分析表單數據時用到此參數
	 */
	public FormGrid queryFormGridByFormId(Form form, boolean showAll,FormShadow formShadow,Long userId, int[] pageLayout);

showData接口:

/**
	 * 顯示錶單數據
	 * @param map   參數
	 * @param html
	 * @param formGrid
	 * @param form
	 * @param rootPath
	 * @param formReadyOnly
	 */
	public void showData(String defaultAliasTable, Map<String, Object> map, StringBuffer html, FormGrid formGrid,FormShadow form,String rootPath,boolean formReadyOnly);
	

FormShadow 表單映射對象:

FormShadow 繼承自form,  主要是擴展了各個維度的詳細信息,第一小節裏,用的是formModel這個word,描述它。

FormGrid  是個網格對象。第一小節裏,用的是viewGrid這個word,描述它。一個是與界面的分析表格視圖(view)對應的類:viewGrid。

在進一步分析實現方法前,先回顧一下第一小節的相關內容:

  1. CommObject
  2. CommForm
  3. FormShadow
  4. Layout
  5. FormGrid

CommObject 是個通用類,把所有對象共有的屬性放到這個類當中:


/**
 * 所有實體類的父類
 */
public class CommObject {

	private int id;
	private String name;
	private int objType;
	private int parent;
	private int generation;
	private int hasChildren;
	private int description;
	private int queryType;    //創建及更改form表單時表示member的查詢函數類型,只在各種維度成員對象中才有意義
	private int formulaType;
	private float position;       //排序key
	
	......

	public boolean equals(CommObject obj) {
		if(obj!=null && this.getId()==obj.getId()){
			return true;
		}
		return false;
	}
	
}                                                                                  

服務端模型CommForm  & FormShadow:




public class CommForm extends CommObject {
	
	
	private int cubeId;
	private int scale = 7;
	......

	public CommForm() {
		setObjType(CommConstants.BG_OBJECT_FORM);
		setParent(CommConstants.BG_OBJECT_ROOT);
	}

	/**
	 * 返回列維度對象
	 * @return
	 */
	public List<CommDimension> getColDims() {
		return getFormDimsByLayoutType(this,CommConstants.LAYOUT_COL);
	}
	
	/**
	 * 返回行維度對象
	 * @return
	 */
	public List<CommDimension> getRowDims() {
		return ...
	}
	
	/**
	 * 返回頁面維度對象
	 * @return
	 */
	public List<CommDimension> getPageDims() {
		return getFormDimsByLayoutType(this,CommConstants.LAYOUT_PAGE);
	}
	
	/**
	 * 返回視點維度對象
	 * @return
	 */
	public List<CommDimension> getViewDims() {
	......
	}
	
	/**
	 * 返回列維度成員對象的集合(展開查詢函數)
	 * @return
	 */
	public List<List<List<DimMember>>> getColMembers() {
		List<List<List<DimMember>>> result = getFormMbrsAsLayoutGroup(this, 
        CommConstants.LAYOUT_COL, true);
		return result;
	}
	
	/**
	 * 返回列維度成員對象的集合(不展開查詢函數)
	 * @return
	 */
	public List<List<List<DimMember>>> getColMembersWithQueryType() {
		List<List<List<DimMember>>> result = getFormMbrsAsLayoutGroup(this, CommConstants.LAYOUT_COL, false);
		return result;
	}
	
	
	/**
	 * 返回行維度成員對象的集合(展開查詢函數)
	 * @return
	 */
	public List<List<List<DimMember>>> getRowMembers() {......
	}
	
	/**
	 * 返回行維度成員對象的集合
	 * @return
	 */
	public List<List<List<DimMember>>> getRowMembersWithQueryType() {......
	}
	
	
	/**
	 * 返回頁面維度成員對象的集合(展開查詢函數)
	 * @return
	 */
	public List<List<DimMember>> getPageMembers() {......
	}
	
	/**
	 * 返回頁面維度成員對象的集合
	 * @return
	 */
	public List<List<DimMember>> getPageMembersWithQueryType() {......
	}
	
	/**
	 * 返回視點維度成員對象的集合(展開查詢函數)
	 * @return
	 */
	public List<List<DimMember>> getViewMembers() {......
	}
	
	/**
	 * 返回視點維度成員對象的集合
	 * @return
	 */
	public List<List<DimMember>> getViewMembersWithQueryType() {......
	}
	
	/**
	 * 返回此Form佈局的列數
	 * @return
	 */
	public int getRowCount() {
		return BusinessMethods.getRowColCount(getRowMembers());
		
	}
	/**
	 * 返回此CommForm佈局的行數
	 * @return
	 */
	public int getColCount() {
		return BusinessMethods.getRowColCount(getColMembers());
	}
	......
}                                            
/**
 * 分析表單映射對象
 *
 */
public class FormShadow extends CommForm {
    
    private List<IcpDimension> colDims = null;
    private List<IcpDimension> rowDims = null;
    private List<IcpDimension> pageDims = null;
    private List<IcpDimension> viewDims = null;
    
    private List<List<List<DimMember>>> colMembers = null;
    private List<List<List<DimMember>>> rowMembers = null;
    private List<List<DimMember>> pageMembers = null;
    private List<List<DimMember>> viewMembers = null;

    
    /**
     * 非明細成員
     */
    private Map<Integer,Integer>      noDetailMap=new HashMap<Integer, Integer>();
    
    
    /**
     * @param commForm
     * @param userId    用戶標識
     * @param ifFilter  是否過濾
     */
    public FormShadow(CommForm commForm,long userId,boolean ifFilter) {
        colDims = commForm.getColDims();
        rowDims = commForm.getRowDims();
        pageDims = commForm.getPageDims();
        viewDims = commForm.getViewDims();
        colMembers = commForm.getColMembers();
        rowMembers = commForm.getRowMembers();
        pageMembers = commForm.getPageMembers();
        viewMembers = commForm.getViewMembers();
        colGroupReadonly = commForm.getColGroupReadonly();
        rowGroupReadonly = commForm.getRowGroupReadonly();
        
        this.setId(commForm.getId());
        this.setName(commForm.getName());
        this.setObjType(commForm.getObjType());
        this.setDescription(commForm.getDescription());
        this.setCubeId(commForm.getCubeId());
        this.setFormFolder(commForm.getFormFolder());
        this.setFormShuoMingId(commForm.getFormShuoMingId());
        this.setScale(commForm.getScale());
        this.setLastModifyTime(commForm.getLastModifyTime());
        this.setApproval(commForm.getApproval());
        ......

    }
    ......

    
}          


前端表格模型FormGrid(viewGrid):


/**
 * 網格對象
 */
public class FormGrid {
	
	private int axisCount;

	private int cubeId;
	private int formId;

	// 行列維度成員
	private int[][] rowMembers = null;
	private int[][] colMembers = null;

	// 頁面
	private int[] pageLayout = null;
	// 視點
	private int[] vpLayout = null;

	// 事實數據
	private DataCell[][] factDatas = null;
	
	//BgForm影子,供生成MDX字符串使用
	private CommForm formShadow = null;
	
	/*
	 * rowGroup表示列布局上的分組信息
	 * 列:
	 * rowCrgoup={0, 5, 10}表示第一組座標0~4 ,第二組座標5~9 ,第三組座標10~rowGroup.lenght
	 */
	private int[] rowGroupInfo = null;
	private int[] colGroupInfo = null;
	
	//行維度
	private int[]  rowDims=null;
	//列維度
	private int[]  colDims=null;
	
	//單元格維度位置
	private int[]  cellDims=null;

	......

	public FormGrid(
			int cubeId,
			int formId,
			int[][] rowMembers, 
			int[][] colMembers, 
			int[] pageLayout,
			int[] vpLayout, 
			DataCell[][] factDatas,
			CommForm formShadow) {

		this.cubeId = cubeId;
		this.formId = formId;
		this.rowMembers = rowMembers;
		this.colMembers = colMembers;
		this.pageLayout = pageLayout;
		this.vpLayout = vpLayout;
		this.factDatas = factDatas;
		this.formShadow = formShadow;
	}

	......
	
	
}
                               

 

由form模型--->grid模型,其中有個概念:佈局(layout)

這裏回顧一下第一小節總結的佈局的概念:



public class FormLayout implements Comparable<FormLayout>{

	private int id;
	private int formId;		//表單
	private int layoutType;	//行 列  視點 頁面 等
	private int ordinal;	//序數   
	private int dimId;			//維度
	private int layoutGroup;  	//組
	private int readonly;  	//是否只讀
	
	......
	
	public int compareTo(FormLayout o) {
		int result = -1;
		if (o != null) {
			result = this.getLayoutType() - o.getLayoutType();
			if (result == 0) {
				result = this.getOrdinal() - o.getOrdinal();
			}
		}
		return result;
	}
}
                                                                      

表單佈局:(layout):

    分析表單的構建,根據的事行維、列維度、頁面、視點的定義,和一個分析表單相關的行、列維度等就是表單佈局。

佈局定義了展示給用戶看的分析表格的結構。

(表單與佈局)

佈局裏的分組,以適應表單中對行維、列維度進行分組的需要;如果不顯示對行、維度進行分組,默認只有第一組;

一組當中,需要依次定義維度;

表單佈局的屬性包括:佈局id,分析表單id,維度id,佈局組、排序、是否只讀等信息。

 這其中佈局(layout_type)類型分爲:行維(2)、列維(3)、頁面(1)、視點(0);

例如,我們需要根據行政區劃按月度分析全省交罰收入情況,可以設置如下的行、列、頁面、視點。

行維:行政區劃(各地財政局)

列維:我們用期間作爲維度;

頁面:我們採用版本、與年度。

視點:交罰項目(科目)。

佈局相關數據庫表如下圖:

(佈局的數據庫表)

分析表單維度成員:

維度成員有在分析表單中有一定順序,分析表單維度成員必然在一個佈局當中。
下面是幾個重點方法之一:

queryFormGridByFormId:

	/**
	 * 以全部下鑽方式打開分析表
	 *通過Form ID查表單,返回FormGrid類實例
	 */
	public FormGrid queryFormGridByFormId(CommForm bgForm, boolean showAll,FormShadow formShadow,Long userId,int[] pageLayout) {
		FormGrid formGrid = null;
		try{
			clearOlapCache();
			/**int[] pageMembers = null; // 存放頁面成員id array,這裏pageLayout 就是pageMembers?
			/ps:第一層key是uerid;第二層key 是formId  後面放的是佈局id數組 
			public  Map<Long,Map<Integer,int[]>>  memoryPageMap=new HashMap<Long, Map<Integer,int[]>>
			**/
			if(pageLayout==null && memoryPageMap.containsKey(userId)){
			     獲取全部pageLayout id//
				 pageLayout=memoryPageMap.get(userId).get(bgForm.getId());
			}
			String mdx = null;
			if (!showAll) {		// 以全部上卷方式展現成本表
				formShadow = buildAllRollUpForm(bgForm);
			}
			mdx = getMdxString(formShadow,pageLayout);
			long start2=System.currentTimeMillis();
			Result result = olapDaoImpl.queryMdx(mdx);
	        // 輸出結果
	        PrintWriter pw = new PrintWriter(System.out);
	        result.print(pw);
	        pw.flush();
			long end2=System.currentTimeMillis();
			System.out.println("構建result耗時"+(end2-start2));
			formGrid = buildFormGridByResult(result, formShadow, pageLayout, formShadow);
			if(pageLayout!=null){
				formGrid.setPageLayout(pageLayout);
			}
			BusinessMethods.fillGroupInfoInGrid(formGrid);
		}catch (Exception e) {
			......
		}
		return formGrid;
	}

這個方法中:一個核心方法代碼是:

            mdx = getMdxString(formShadow,pageLayout);  //動態構造mdx語句!
            Result result = olapDaoImpl.queryMdx(mdx);

動態構造mdx方法:

	/**
	 * 對外提供取得多維查詢語句的接口
	 * @return 多維查詢語句
	 */
	private  String getMdxString(FormShadow formShadow,int[] pageLayout){
		formShadow.getNoDetailMap().clear();
		formShadow.getShareMemberMap().clear();
		IcpCube cube=formShadow.getCube();
		String cubeName=cube.getName();
		List<List<List<DimMember>>> colMemberObjs = formShadow.getColMembers();
		List<List<List<DimMember>>> rowMemberObjs = formShadow.getRowMembers();
		List<List<DimMember>> pageMemberObjs = formShadow.getPageMembers();
		List<List<DimMember>> viewMemberObjs = formShadow.getViewMembers();
		String onCols = conMembers(colMemberObjs,formShadow);
		String onRows = conMembers(rowMemberObjs,formShadow);
		String onWheres = conWhereMembers(pageMemberObjs,viewMemberObjs,pageLayout,formShadow);
		String mdxString = makeMdxString(cubeName,onCols,onRows,onWheres);
		String withMemsStr = conWithMembers(colMemberObjs,rowMemberObjs,formShadow);
		if(withMemsStr!=null){
			mdxString = withMemsStr + mdxString;
			mdxString=replaceTempFormulaMember(mdxString, formShadow);
		}
		return mdxString;
	}

這個方法中,重點是根據後端模型,動態構建:

mdx語句被劃分成cube, col、row、where  四個部分,且動態生成之。

col、row mdx 子部分語句的生成方法conMembers如下:

	/**
	 * 根據行/列成員對象連接成員字符串
	 * @param formShadow 
	 * @param membersList 行成員/列成員對象的集合
	 * @return 連接成員字符串
	 */
	 private static String conMembers(List<List<List<DimMember>>> membersList0, FormShadow formShadow) {
		StringBuffer strBuffer=new StringBuffer();
		if (membersList0 != null&&membersList0.size()>0) {
			strBuffer.append("{");
			for (int k = 0; k < membersList0.size(); k++) {   //遍歷行和列上的維度
				List<List<DimMember>> membersList = membersList0.get(k);
				if (membersList != null&&membersList.size()>0) {
					for (int i = 0; i < membersList.size(); i++) {	//遍歷一個維度上的分組
						strBuffer.append("{");
						List<DimMember> members=membersList.get(i);
						for (int j = 0; j < members.size(); j++) {	//遍歷組成員
							DimMember member=members.get(j);
							CommDimension dim=member.getDimension();
							String memberN=member.getNavigation();   /**負責生成類似"[Year].[YearLy].[2010年]" 語句
                            **{[Year].[YearLy].[2009年], [Year].[YearLy].[2010年], **[Year].[YearLy].[2011年], [Year].[YearLy].[2012年], **[Year].[YearLy].[2013年], [Year].[YearLy].[2014年], **[Year].[YearLy].[2015年], [Year].[YearLy].[2016年],
							**[Year].[YearLy].[2017年]}**/
							......
							strBuffer.append(memberN);
							if (j != members.size() - 1) {
								strBuffer.append(",");
							}
						}
						strBuffer.append("}");
						if (i != membersList.size() - 1) {
							strBuffer.append("*");  //這裏是乘積的運算符?
						}
					}
				}
				if(k!=membersList0.size()-1){
					strBuffer.append(",");
				}
			}
			strBuffer.append("}");
		}
		return strBuffer.toString();
	}

生成mdx條件語句部分:

	 
	 /**
	  * 根據頁面、視點成員對象連接成員字符串
	  * @param pageMembersList 頁面成員對象的集合
	  * @param viewMembersList 視點成員對象的集合
	 * @param formShadow 
	    *param  pageLayout 參數存放的是由維度組成頁面數組中,
		*選中的維度成員,初始化用的是一個維度中第一個維度成員,
		*如果用戶有選擇,就用用戶的選擇,用戶選擇在pageLayout 這個數組中
	  * @return 連接成員字符串
	  * 
	  
	  */
	 private static String conWhereMembers(List<List<DimMember>> pageMembersList,List<List<DimMember>> viewMembersList, int[] pageLayout, FormShadow formShadow) {
		StringBuffer strBuffer=new StringBuffer();
		if (pageMembersList!=null&&pageMembersList.size()>0) {
			strBuffer.append("(");
			int dimCount=pageMembersList.size();
			for (int i = 0; i < dimCount; i++) {	//遍歷所有頁面維度  頁面上可以存在多個維度
				List<DimMember> members=pageMembersList.get(i);
				if (members.size()>0) {
					DimMember firstMember=members.get(0);  //只取得第一個維度成員
					CommDimension dim=firstMember.getDimension();
					DimMember member = null;
					String memberN=null;
					if(pageLayout==null){
						member=members.get(0);
						dim=member.getDimension();
						memberN=member.getNavigation();
						strBuffer.append(member.getNavigation());
						if(member.getHasChildren()==1){
							//非明細成員
							formShadow.getNoDetailMap().put(member.getMemberId(), member.getDimension().getId());
						}else{
							memberN=buildShareMember(member, dim, formShadow,memberN);
						}
					}else{
						member = new CacheFortified().getMemberInDimByMemberId(pageLayout[i], dim.getId());
						memberN=member.getNavigation();
						if(member.getHasChildren()==1){
							formShadow.getNoDetailMap().put(member.getMemberId(), member.getDimension().getId());
						}else{
							memberN=buildShareMember(member, dim, formShadow,memberN);
						}
						strBuffer.append(memberN);
					}
				}
				if (i != dimCount-1||viewMembersList!=null&&viewMembersList.size()>0) {
					strBuffer.append(",");
				}
			}   //結束遍歷所有頁面維度
		}
		
		if (viewMembersList!=null&&viewMembersList.size()>0) {
			if (strBuffer.length()==0) {
				strBuffer.append("(");
			}
				int dimCount=viewMembersList.size();
				for (int i = 0; i < dimCount; i++) {
					List<DimMember> members=viewMembersList.get(i);
					if (members.size()>1) {
						return null;						
					}
					if (members.size()==1) {				
						DimMember member=members.get(0);
						String   memberN=member.getNavigation();
						CommDimension dim=member.getDimension();
						if(member.getHasChildren()==1){
							formShadow.getNoDetailMap().put(member.getMemberId(), member.getDimension().getId());
						}else{
							memberN=buildShareMember(member, dim, formShadow,memberN);
						}
						strBuffer.append(memberN);
					}
					if (i != dimCount-1) {
						strBuffer.append(",");
					}
				}
		}
		if (strBuffer.length()>0) {
			strBuffer.append(")");
		}
		return strBuffer.toString();
	}

拼接整個mdx語句:

	/**
	 * 
	 * @param cubeName 立方體名
	 * @param onCols 列成員字符串
	 * @param onRows 行成員字符串
	 * @param onWheres 頁面視點成員字符串
	 * @return  多維查詢語句
	 */
	 private static String makeMdxString (String cubeName, String onCols, String onRows,String onWheres){
		String mdxString = null;
		if (onCols!=null || onRows!=null) {
			mdxString = "SELECT ";
			String addStr = "{[Measures].[金額]} ON COLUMNS";
			if (onCols!=null) {	
				addStr = (onCols + "*")+addStr;
				if (onRows!=null) {
					addStr += ",";
				}
			}
			mdxString += addStr;
			if (onRows!=null) {	
				mdxString += (onRows + " ON ROWS");
			}
			mdxString += (" FROM [" + cubeName + "]");
			if (onWheres!=null && !onWheres.equals("")) {			
				mdxString += (" WHERE " + onWheres);
			}
		}
		return mdxString;
	}

 

mdx語句拼接完成,查詢結構後,如何顯示,下一章就總結這個。

 

 

 

                                     

                

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章