mondrian源码分析
1. 概述........................................................................................................................................... 1
1.1. 若干概念 ....................................................................................................................... 1 1.2. 架构 ............................................................................................................................... 2 2. 静态类包分析 ........................................................................................................................... 3
2.1. 包解释 ........................................................................................................................... 3 2.2. Schema manger部分 .................................................................................................... 4 2.3. 包mondrian.calc ........................................................................................................... 4 2.4. 包mondrian.olap—接口............................................................................................... 5
2.4.1. mdx函数包 ....................................................................................................... 5 2.4.2. funCall ............................................................................................................... 5 2.4.3. Query类 ............................................................................................................ 6 2.5. 包mondrian.rolap—计算层 ......................................................................................... 6
2.5.1. 成员读取包MemberReader ............................................................................. 6 2.5.2. 单元格读取CellReader .................................................................................... 8 2.5.3. RolapResult类 ................................................................................................ 11 2.5.4. RolapEvaluator类 ........................................................................................... 12 2.5.5. 关于排序 ......................................................................................................... 12 2.6. 聚集层Star layer ........................................................................................................ 13
2.6.1. 概述 ................................................................................................................. 13 2.6.2. 聚合装载过程 ................................................................................................. 13 2.6.3. segment详解 .................................................................................................. 14 2.6.4. 缓存失效控制 ................................................................................................. 17
3. 交互管理层 ............................................................................................................................. 18
3.1. 初始化MondrianModel.............................................................................................. 18 3.2. 获取结果集 ................................................................................................................. 19 4. 修改点..................................................................................................................................... 22
4.1. mondrian角色参数化 ................................................................................................ 22 4.2. 缓存失效控制 ............................................................................................................. 22 4.3. connection创建参数优化 .......................................................................................... 23 4.4. 维度成员排序 ............................................................................................................. 23 4.5. 对mondrian bug的修正 ............................................................................................ 24 4.6. to-do List ..................................................................................................................... 24
1. 概述
1.1. 若干概念
成员(member):成员是代表维度中一次或多次数据出现的项。度量值也可以算作一个维度,
因此一个具体度量值项也可以作为一个成员。
元组(tuple):是向量,用于定义来自多维数据集的数据切片;它由来自一个或多个维度的单个成员的有序集合组成。元组用于标识来自多维数据集的特定多维数据块;由来自多维数据集中各个维度的一个成员组成的元组完全描述单元值。换言之,元组是一种成员向量。 例如:(时间.[下半年], 路线.非陆地.航空),由单个成员组成的元组也可括在圆括号内,但这不是必需的。
单元(cell):多维成员的交集创建单元,可以是单个单元或单元块。元组唯一标识多维数据集中的一部分;它不必指某个特定单元,也不必包括多维数据集中的所有维度。
集合(set):集合是零个、一个或多个元组的有序集合。集合最常用于在 MDX 查询中定义轴维度和切片器维度,并且同样可能只具有单个元组或可能在某些情况下为空。下面的示例显示具有两个元组的集合:{ (时间.[上半年], 路线.非陆地.航空), (时间.[下半年], 路线.非陆地.海路) }
1.2. 架构
mondrian总共包含四个层次:表示层,计算层,聚集层,存储层.。
表示层(presentation layer):指最终呈现在用户显示器上的,以及与用户之间的交互,有许多方法来展现多维数据,包括数据透视表,饼,柱,线状图.
计算层(dimensional layer):分析,验证,执行MDX查询. 一个mdx查询语句会有多个处理阶段。先是计算轴,然后是轴上的单元值。为效率起见,计算层批量将单元请求发送到聚集层。请求转换器允许程序操作存在的请求,而不是为每个请求从头构造mdx请求。元数据描述了计算模型和它怎么匹配到关系模型。
聚集层(star layer):一个聚集指内存中一组计算值(cell),这些值通过维列来限制.计算层发送单元请求,如果请求不在缓存中,或者不能通过旋转聚集导出的话,聚集层向存储层发送请求. 聚集层是一个数据缓冲层(cache),从数据库来的单元数据,聚合后提供给计算层。聚集层的主要作用是提高系统的性能。
存储层:提供聚集单元数据(cell)和维表的成员(member),这些层可以不在同一机子上,但是计算和聚集层必须在同一台机子上。有三种需要存储的数据:事实数据(事实表)、维度表和聚集数据(即聚合表)
架构图如下:
2. 静态类包分析
2.1. 包解释
mondrian.calc 提供编译好的表达式。
mondrian.gui 设计Mondrian schema的图形接口 mondrian.i18n 国际化和本地化工具
mondrian.mdx 为mdx表达式定义解析树
mondrian.olap 核心包,定义了连接和schema的元模型,用来执行查询 mondrian.olap4j 中间层,olap服务器的驱动,用来代替jolap的 mondrian.recorder 任务处理记录接口
mondrian.rolap olap包的数据访问层的实现
mondrian.spi 用户自定义扩展的服务端支持接口
mondrian.tui Mondrian文本用户接口 mondrian.udf 用户定义方法 mondrian.util Mondrian工具包
mondrian.web Mondrian的servlet和tag库 mondrian.xmla xml for analysis API的实现
2.2. Schema manger部分
Mondrian.rolap.RolapSchema类是mondrian schema的核心类,该类在在建立RolapConnection时被建立,但是有个schema Pool维护着schema的缓存,参见RolapSchema.Pool内部类。
在RolapSchema对象生成之前,首先有个原始的MondrianDef$Schema对象,该对象相当于mondrian schema xml文件的简单对应,由xml解析器直接生成。具体来说首先由xml文件解析成dom对象,然后再生成更加结构化的MondrianDef$Schema,最后再load成更加高级的RolapSchema对象,具体参见RolapSchema.load(String catalogUrl,String catalogStr);
以MondrianDef$Schema或RolapSchema对象为根,都还会有一系列的内部类去对应于mondrian schema下的子元素。
如果一个Hierarchy的hasAll为true,则会有一个all member,对应着也需要一个虚拟的all level,位于该Hierarchy的levels[0]位置。
2.3. 包mondrian.calc
Calc是所有可计算表达式的基接口。在mondrian中关于表达式有如下两个概念: ? The logical language of parsed MDX fragments (Exp). ? The phyiscal language of compiled expressions (Calc).
两种语言可以允许我们将逻辑语言(即mdx语言) 和物理语言(how it is to be evaluated) 分开. 物理语言对类型的要求更加严格, and certain constructs which are implicit in the logical language (such as the addition of calls to the
Calc接口针对各种类型有许多子接口:其中IntegerCalc, BooleanCalc, DoubleCalc, StringCalc是数值型的,MemberCalc, LevelCalc, HierarchyCalc, DimensionCalc则用于olap模型中的元素。
每一个子接口有都有一个实现的虚基类:AbstractIntegerCalc, AbstractBooleanCalc, AbstractDoubleCalc, AbstractStringCalc, AbstractMemberCalc, AbstractLevelCalc, AbstractHierarchyCalc, AbstractDimensionCalc。
表达式(Expression)通常由表达式编译者(ExpCompiler)创建。对于一个给定的表达式通常有若干个evaluation策略,表达式编译者在编译过程中可以给我们一个选择的机会。
2.4. 包mondrian.olap—接口
Mondrian的核心包,定义了连接对象和完整的olap模型结构元对象,并且允许执行mdx查询语句。
2.4.1. mdx函数包
mondrian.olap.fun 定义了mdx内置的函数集。 函数(function)的定义: 参数 name 描述 函数名 例子 \\\a dimension.\signature 函数标志 description 函数描述 flags Encoding of the syntactic type, return type, and parameter types of this operator. The encoding is \described below. The flags field is an string which encodes the syntactic type, return type, and parameter types of this operator. The first character determines the syntactic type, as described by FunUtil.decodeSyntacticType(String). The second character determines the return type, as described by FunUtil.decodeReturnCategory(String).
The third and subsequence characters determine the types of the arguments arguments, as described by FunUtil.decodeParameterCategories(String).
For example, \property syntax (p) which returns a set (x) and takes a dimension (d) as its argument\
2.4.2. funCall
A FunCall is a function applied to a list of operands.
The parser creates function calls as an unresolved function call.
The validator converts it to a resolved function call, which has a function definition and extra
type information。参见:Mondrian.olap. ResolveFunCall UnresolveFunCall
2.4.3. Query类
用于mdx 查询。
2.4.3.1. 创建query
创建:Connection.parseQuery(java.lang.String)。
创建mondrian.olap.Parser类,基于java_cup实现,调用其parse或debug_parse()方法,里面最主要 是CUP$Parser$do_action()方法,内部针对所有语句词句做了解析。
其中case75:生成最后的mondrian.olap.Query对象,内部调用parser.makeQuery()方法,内部初始化时会再调用query的resolve,mondrian.olap.QueryAxis的resolve(),进而会执行数据库。
2.4.3.2. 执行query
执行:Connection.execute(mondrian.olap.Query) 返回结果: Result.
有些查询从缓存中读取,非常迅速。
当也有些需要花费些时间,这时可以通过MondrianProperties.QueryTimeout参数设置timeout。
如果想控制返回结果不至于太大,可以通过MondrianProperties.QueryLimit参数控制返回的cell数目。
在查询执行的任何时候,另一个进程都可以通过cancel()方法取消该查询,此时Connection.execute(Query)会抛出异常。
2.5. 包mondrian.rolap—计算层
实现最终的实际的olap数据访问功能,包括读取维度成员值和cell值。
2.5.1. 成员读取包MemberReader 2.5.1.1. 概述
包路径:Mondrian.rolap。
该部分的起点是RolapEvaluator类。当一个类似于“member.children”的成员表达式被请求时, RolapEvaluator将调用RolapSchemaReader对象. RolapSchemaReader将再负责调用各个MemberReader对象(每个维度一个memberReader)。在大部分场合下,将使用SmartMemberReader 来迅速返回所需要的维度成员值。
2.5.1.2. SmartMemberReader
SmartMemberReader实现了MemberReader接口,它实现了维度成员及其子成员的缓存,如果有一个成员位于缓存中,则还会有一个其子成员的列表。它同时缓存了level下的成员们。该类主要的成员有:
source:MemberReader,用于实际从数据库中读取维度成员值。 mapMemberToChildren:map,实现成员及其子成员的映射,
key为RolapMember,value为List
mapKeyToMember: map ,实现所有成员的缓存,其中的key为MemberKey mapLevelToMembers: map,实现级别及其所有成员的映射,
key为RolapLevel,value为List
上述的source其实为mondrian.rolap.SqlMemberSource类,该类中反过来又存储了SmartMemberReader对象,作为其cache成员属性。
成员读取过程:
? smartMemberReader.getMemberChildren(parentMembers,children,constrain);
? 最终通过source.getMemberChildren()…,其中反过来会把找到的children赋予
mapKeyToMember。
? 最终除了将结果返回在children输出参数中,同时也对mapMemberToChildren赋
值了。
另外,smartMemberReader.getMembersInlevel()实现了对mapLevelToMembers的缓存。在new RolapEvaluator()时被调用。在读取时会对成员进行order by(如果设置了ascending的话。) 2.5.2. 单元格读取CellReader 2.5.2.1. 概述
包路径:Mondrian.rolap。
Cells会被求值多次。第一次时, Evaluator使用FastBatchingCellReader来求值。当一个单元被求值时,evaluateCurrent()被调用。此时FastBatchingCellReader并没有被调用,而是为那个cell记录了一个 CellRequest并且return (not throw) an exception。在所有的cells都有了对应的CellRequests之后, Aggregation会生成 SQL,以一个单独的sql请求来载入所有的cells。然后由AggregatingCellReader 重新计算cells,从缓存中返回cells值。
2.5.2.2. FastBatchingCellReader类
主要方法,Object get(Evaluator evaluator)
? 首先根据当前的上下文环境(即一组members)创建cellRequest,cellRequest中包
含了所有必要的从star中取值的信息。该组members的交集便是要求值的单元格,其中切片轴上的成员和其他轴上的成员完全同等对待;其中度量轴上的成员要求上StoredMeasure(非计算成员CaculatedMember);度量值上的成员位于第一个。通过调用request的addConstrainedColumn()方法把各member对应的column
和value(属StarColumnPredicate)值加至到request中.
? 调用AggregationManager.getCellFromCache(request,pinnedSegments)方法从
缓存中获取cell值。首先根据request中的列组索引标识从缓存中获取
aggreation缓存对象,如果为空说明缓存还未建立则直接返回null,如果有值则调用aggregation.getCellValue(measure,colValueKeys)方法获取缓存的cell值;getCellValue内部首先会根据measure查找匹配的segment,然后调用segment.getCellValue(keys)从segment的dataset缓存集中查找相应的cell值。 ? 如果getCellFromCache返回为null则调用recordCellRequest()记录需求。这
些cell request会被组织成多个cell request batch,以便将来聚合层进行批读取以提高效率。关于batch的详细讨论参见下面Batch类章节。
? 上层会在适当的时候调用batchCellReading.loadAggregations()以实际读取这些cell
值,前提是batches对象中已有cellRequest了。每个batch的读取参见batch. loadAggregation()方法,最终调用聚合层的方法,参见aggreation.load(….)。
2.5.2.3. FastBatchingCellReader.Batch类
每个batch对应与一组特定的columns环境下的cell求取(具有相同的列和列值(列值是具体的值,不会是“all”值));从batch的属性可以看出batch包含了哪些上下文:
? RolapStar.Column[],这个指明了基于哪些列(也即基于哪些维度,包括切片维度)
进行读取;
? Set
有多个(毕竟是批处理,一次请求多个);
? MeasureList,指明求取哪些度量值上的cell(度量值本质是度量维上的限定值)。 ? BitKey,该batch的唯一索引。
如图所示的一个mdx查询结果界面:
此时会产生两个batch,每个batch最终可能会产生若干segment,segment是cells的集合,segment数和度量值个数相同。
? 一个batch是(其中“当量数/适应交通量=拥挤度”,拥挤度是计算成员),最终产生
3个segment,每个segment只有一个cell: ? (地市=’宁波市’,measure=’观察里程’) ? (地市=’宁波市’,measure=’当量数’) ? (地市=’宁波市’,measure=’适应交通量’)
? 另一个batch是(其中的G310等是路线代码,最终过滤掉空值后就剩下两个了) ,
最终产生3个segment,每个segment有多个cell:
? (地市=’宁波市’,roadId in (G310,G322,G210,S321….),measure=’观察里程’) ? (地市=’宁波市’,roadId in (G310,G322,G210,S321….),measure=’当量数’) ? (地市=’宁波市’,roadId in (G310,G322,G210,S321….),measure=’适应交通量’)
2、 …
2.5.3. RolapResult类
RolapResult 是一个运行中的请求的结果集。
Mondiran的执行结果由RolapResult类表单,由于mdx查询语句本身就包含on rows(行轴上)、on columns(列轴上)和where部分(切片轴上),结果集中相对应的为ROlapAxis对象,这其中有个sliceAxis对象。因此结果集是由若干ROlapAxis对象和一个RolapCell组构成的。每个axis对象又由若干Position对象组成,每个Position对象又可能由若干
member组成(注意一个postion会横跨多个维度的成员)。注意ROlapAxis是抽象类,实际的对象类可能随着不同的轴是不同的。如图:
图中,column轴上两个position(每个position含有一个成员),分别是:
[[Measures].[YJD]] [[Measures].[GCLC]]
Row轴上有三个position(每个position含有二个成员),分别是:
[[dimLX].[All dimLXs], [dimTime].[All dimTimes]]
[[dimLX].[All dimLXs].[宁波—梁辉], [dimTime].[All dimTimes]] [[dimLX].[All dimLXs].[同江-三亚], [dimTime].[All dimTimes]]
切片轴上则有一个position:[[dimStation].[All dimStations].[宁波市]]
单元值们则放置在RolapResult中的cellInfos对象里,属CellInfoContainer接口,其中存放着CellInfo,并通过Cellkey进行索引。
CellKey:用于在maps里访问cellinfo时使用的键值,根据cell的位置来决定键值。CellKey共有四个默认实现,及zero、one、two、three和many版的实现,分别对应着轴的个数。这些类中关键的属性便是存储各轴的位置值。
CellInfo、CellInfoContainer:内部类。CellInfo包含了一个cell所需要的所有信息(最关键的包含value值和一些formatter设置);最终将作为构造ROlapCell对象的参数。CellInfoContainer显然是cellInfo的容器,并使用CellKey来索引。
ROlapCell:最终返回给jpivot的cell单元值。
2.5.4. RolapEvaluator类
最终负责在多维环境中执行mdx表达式。
该类中维护一个很重要的对象,即currentMembers,该上下文对象针对每个维度都包含了一个成员;通过setContext方法用来设置当前维度,以开始计算当前维度组合下的表达式值。
2.5.5. 关于排序
排序有单元值排序和维度成员排序两种场景,但单元值排序优先级更高,只有当单元值排序相当时,才再对成员值进行排序。
单元值排序有六种,升序、降序、打破Hierarchy升序、打破Hierarchy降序、top count、bottom count。其中前四种参见:FunUtil.SortMembers()中的方法,这是总入口,其中有BreakMemberComparator、HierarchicalMemberComparator、BreakArrayComparator、HierarchicalArrayComparator等排序器(Array包含多个member,此时相当于cell上下文环境由多个维度参与)。
对于array类排序,参见其中的public int compare(Member[] a1,Member[] a2)方法;
对于单member类排序,参见其中的public int compareInternal(Membera1,Member a2)方法。
维度成员排序似乎有三种:按照orderKey、按照ordinary、按照值(参见RolapMember.compareTo()方法,最终相当于按字符串或数值比较,其中第三种内部又会用到ascending属性),这个是我们自己新增的属性。具体参见FunUtil.compareSiblingMembers()方法。
2.6. 聚集层Star layer
2.6.1. 概述
包mondrian.rolap.agg,管理聚合缓存,这些缓存中包含着各单元格值。
RolapStar中含有aggregation(聚合),一个aggregation是针对一组columns的,该聚合可以包含多个segment,同一个aggregation中的每个segment都将覆盖到相同的列集合;每个segment表达了一组cell值,这些cell自然是由具体的列值和一个必备度量值(如下面的unit sales)来限定,如:(Unit sales, Gender = 'F', State in {'CA','OR'}, Marital Status = anything),由于其中的列值可能会取多个,因此最终表达的cell值也可能是多个。RolapStar中有一个aggregations,是一个map对象,通过request的constrainedColumnsBitKey来索引一个aggregation。
2.6.2. 聚合装载过程
实际中无论是底层的单元值还是聚合后的单元值都是放在聚合对象aggregation中的。以aggregation.load(colums,measures,predicates,pinnedSegments)为入口:
? 参数中的除了measure不一样外,其限定的列(colums)及列值(prediactes)都是一
致的。因此转换成对对若干segment[]的求值: Segment.load(segment[],….),在该方法内部:
? 首先根据segments中的信息生成sql查询语句,有两个不同的生成类:
AggQuerySpec和SegmentArrayQuerySpec,前者用于找到聚合表情况下的sql语句生成,后者用于基于原始表的sql语句生成。具体可以参见它们的generateSqlQuery()方法,这里注意对以distinct count有不同的生成方法。Sql生成的核心类是sqlQuery,类似于交换系统中的QuerySqlFactory类。注意:聚合操作如avg、sum等都最终还是利用sql语句实现的,并非mondiran自己
实现这些聚合功能。
? 利用jdbc,执行sql语句,获取到jdbc 结果集。参见
mondrian.rolap.RolapUtil.executeQuery()方法。
? 解析结果集,将结果集中的数据填充到rows[][]二维数值中,并且把各列的值
也填充好。如图:
结果集每条记录的值如宁波市、G010….,前面两个是维度列值,后面几个是度量值。
各列的值(其中第0项值为:[宁波市]):
? 决定采用稀疏性(sparse)还是稠密性(dense)SegmentDataSet存储;并创建该
空的DataSet对象。每个segment关联一个DataSet对象;但其稀疏性还是稠密性都是一致的。注意dataset中单元值的个数可能是1个或多个,是由各限定列的指定值个数乘积,若所有限定列都取单值,则显然最终决定一个唯一的单元。
? 将上述的rows中间集转换到SegmentDataSets集中。最后再分拣给每个
segment,确保每个segment的setData(SegmentDataSet)被调用。
? 若干排序:ValueColumnConstraintComparator
2.6.3. segment详解
? 下图是有两个限定列的两个segment的描述(注:其中roadid列虽然指定了8个候选
值,但由于使用了空行/列过滤,最后只剩下两个路线有值,故最后segment结果
集的单元数也只有两个,对应于G010和G318的):
其中第二个对应的dataset为:
[317.769, 120.604]
对应的透视界面为(参见其中的“观测里程”度量值,与上面的dataset一致):
? 再譬如有三个限定列的segment描述,它们位于另一个aggreation对象中:(其中
timeId列的any代表所以可能的时间值,共有2003~2005三个年,所以最终该segment共有3个cell值)
对应的dataset为:
[129.910, 129.909, 57.950]
对应的透视界面为(显然该aggreation还有另外一个segment,其中的roadId对应于G010—宁波梁辉):
? 再譬如维度中有多个层次的情况时,一个维度会对应多个列:
Query query = connection.parseQuery( \
\
\ \ \
\ \
Result result = connection.execute(query);
该语句执行后产生的segment分别为(除了第一个外,其他segment都会包含多个cell,因为它们的限定列中含有多值的情况):
Year Nation Unit Sales Segment 1997 USA xxx YN#1 Predicates: Year=1997, Nation=USA Year Nation State Unit Sales Segment 1997 USA OR xxx YNS#1 1997 USA WA xxx Predicates: Year=1997, Nation=USA, State={OR, WA} Year Quarter Nation Unit Sales Segment 1997 Q1 USA xxx YQN#1 1997 Q2 USA xxx Predicates: Year=1997, Quarter=any, Nation=USA Year Quarter Nation State Unit Sales 1997 Q1 USA OR xxx Segment 1997 Q1 USA WA xxx YQNS#1 1997 Q2 USA OR xxx 1997 Q2 USA WA xxx Predicates: Year=1997, Quarter=any, Nation=USA, State={OR, WA}
2.6.4. 缓存失效控制
为了提高访问效率,必须要使用缓存;但使用缓存必须在数据库底层数据发生变化时做好数据的失效和刷新工资。
老版的做法非常粗略:mondrian.rolap.RolapSchema.clearCache();这意味着连mondrian schema都被失效,与之关联的全部缓存也都被失效。
新版的做法是引入了mondrian.olap.CacheControl接口,可以很精细地由应用控制缓存。为此mondrian引入了cell region的概念,region是由一个或多个成员定义出的多维空间(定义时可以指定是否包含这些成员的子成员)。为了使缓存失效,你需要首先定义一个region,然后告诉mondrian失效该region内的所有cells;为了确保一致性,mondrian还会自动地把这些cell的聚合单元也一起失效。具体参见
CacheControl.CellRegion类。最终调用cacheControl.flush(cellRegion)。
具体失效时,还是针对segment进行的,因为mondiran中的cell缓存是通过segment组织的。失效发生时,可能segment中的部分cell无用了,但mondrian并不会真正删除这些cell,而是strengthened其predicates,例如失效前的某个segment为:
1997 USA OR xxx 1997 USA WA xxx
Predicates: Year=1997, Nation=USA, State={OR, WA}
然后Crossjoin([time].[1997].[Q2],[Customer].[USA].[OR])的cellRegion失效,此时该segment变为:
1997 USA OR xxx 1997 USA WA xxx
Predicates: Year=1997, Nation=USA, State={WA}
目前这样处置未必最合理,因为会有内存泄露。将来可能根据该segment中的cell达到一定的失效阀值,就彻底把该segment抛弃掉。
Segment的合并也是将来应考虑的事情,以减少segment碎片。 维度成员值的缓存控制目前mondrian还未提供控制接口。
但是metaCacheControl,例如控制schema xml等还是提供不少接口了,例如: ? mondrian.rolap.RolapSchema.clearCache() ? mondrian.olap.MondrianServer.flushSchemaCache()
? mondrian.rolap.cache.CachePool.flush()
? mondrian.rolap.RolapSchema.flushRolapStarCaches(boolean) 该方法不失效
schema,但失效缓存,在应用层还不想使用cellRegion失效缓存前,可以适度使用。
? mondrian.rolap.RolapSchema.flushAllRolapStarCachedAggregations() ? mondrian.rolap.RolapSchema.flushSchema(String,String,String,String) ? mondrian.rolap.RolapSchema.flushSchema(DataSource,String)
3. 交互管理层
MondrianModel 是JPivot中用来展现一个MDX查询的所有原数据的模型。这里主要讲MondrianModel的三个方法,initialize(),getResult(),destroy()。
3.1. 初始化MondrianModel
1、 在
jpivot.mondrian.MondrianModel
执行
initialize()方法之前,先用
MondrianModelFactory.Config对象为MondrianModel对象设置jdbc,schema url等参数。 2、 调用
mondrian.olap.DriveManager.getConnection()方法,创建并返回
mondrian.rolap.RolapConnection对象,置于变量monConnection中;创建连接的参数指定详见mondrian.olap.RolapConnectionProperties。
2.1 在创建RolapConnection对象的构造函数中,会用对象池技术建立mondrian.rolap.RolapSchema对象。该对象会被缓存在对象池中,只会创建一次(RolapSchema对象中还有一个大对象xmlSchema,是纯定义性质的,Mondrian.olap.MondrianDef.Schema);这里需要注意的是:
? RolapSchema对象的建立有几种方法:首先schema字符串内容本身可以通过参数
直接传递进来;也可以通过url地址指定;还可以通过自定义的DynamicSchemaProcessor类来给定(这样就有机会动态对schena内容进行修改)。 ? RolapSchema对象本身的建立一般是从缓存中读取,没有时则创建。但若使用了上
述的DynmicSchemaProcessor则每次都会重新创建;并且若指定了UseContentChecksum参数 (将使用md5对字符串内容编码),若UseContentChecksum为true,则 md5编码值与以前的不同,则即使存在了RolapSchema对象也会重新创建。可与DynamicSchemaProcessor属性一起用。
? 在Mondrian新版本3.0中,RolapConnectionProperties 中除了定义了
DynamicSchemaProcessor 和UseContentChecksum 参数,还新增了UseSchemaPool属性,如果该属性为false,将禁用schema缓存。 2.2 最后调用RolapSchema.createRole()方法创建该连接关联的role对象(如果指定了的话);并创建ROlapSchemaReader对象(和role绑定的,读取时会进行必要的控制)。
2.3 根据传入的参数设置locale,没有的话按照en处理。
3、调用connection的parseMDX(),最终调用ConnectionBase.parseQuery()方法准备好一个mondrian.olap.Query对象,置入成员变量monQuery;query对象由mondrian.olap.parse对象生成,该对象是一个自动生成的类,使用了javacup技术。
3.2. 获取结果集
1、 之前确保MondiranModel中的monQuery对象已经创建好。
2、 Jpivot中,界面会调用jpivot.table.TableCompont对象的render方法进行绘制,最终
会调用MondrianModel.getResult()的方法,但返回的是jpivot.olap.model.Result对象,并非mondrian.olap.Result对象。
2.1 首先执行MondrianQueryAdapter对象queryAdapter的onExecute()。得到mdx
语句。
2.2 调用monConnection.execute(monQuery)获取mondrian result对象;内部会创建
new ROlapResult(monQuery,true),RolapResult得到的是运行中请求的结果,其中ture代表要实际装载数据。 2.3 内部会进行Evaluator算法:
显式成员:在轴上的成员。
隐式成员:不在轴上的成员,可能是在函数中。
如果一个维度的成员都是隐式成员,相当于在切片上使用默认成员。
有两种特定维度:时间维度和度量维度。 时间维度
度量维度的默认成员是没有所有的××的,约定成俗的是第一个度量。
首先,RolapEvaluator会被创建。创建过程中,它会从每个hierarchy中获取一
个member,每个member是hierarchy的默认成员。对于大多数hierarchy,这个默认成员就是所有成员。可能有两种例外:1)hierarchy没有所有成员;2)hierarchy有所有成员但是它不是默认成员。这样的话,默认成员仍然会被使用,但是可能会有问题。
然后载入所有无所有成员的hierarchy的根成员,载入非默认成员的所有的成员。
2.4 内部会进行Evaluator算法,三大步骤:
? Determine axes步骤:决定每个轴上的所有成员,但此时还不保存信息(不
创建RolapAxis)。
确定切片轴上的成员。轴上发现的成员都会被加入到AxisMember中。如果该成员是度量,则切片指明请求的度量,并将该度量放入evaluator的context中(取代cube中的第一个度量,即默认度量)。其它的切片成员也会被放入evaluator的context的。
? execute axes步骤:为每个轴保存所有成员,创建RolapAxis对象(重复了第
一步骤,只是此时创建了RolapAxis对象,该对象是结果集中的维度成员值部分)。执行第一步和第二步时确保数据已经载入。
? get cell步骤:根据轴上的成员值计算并存储每个单元值(结果集的cell值部
分),参见executeBody()方法,内部主要调用executeStripe()方法, ? ExecuteStripe()方法内部采用递归方法依次获取每个单元值,从行轴
开始(轴位置索引最大的那个),针对每个行轴的mondrian.olap.position进行循环,在一个循环内再针对列轴的每个position进行循环,求得交叉点的cell值。每个position由一个或多个维度成员值组成,具体的个数同该轴上交叉的维度数;在计算某cell值之前,会依次用该cell的member值(包括切片上的维度值)设
置
好
evaluator
的
context
。
最
后
调
用
evaluator.evaluateCurrent()方法获得当前context下的cell值。
? Evaluator.evaluateCurrent()内部可能会执行到间接递归调用,
这个发生在执行计算成员值的情况下;此时为了计算成员,首先需要获取其他度量值,而为了获取这些前置的度量值又要调用
evaluateCurrent()方法。EvaluateCurrent内真正获取单元值的地方是调用:cellReader.get(this);此处使用的cellReader为
FastBatchingCellReader类,调用其get()方法,内部实际是从
aggregation的缓存中获取单元值。
? 最后所有的单元对象CellInfo存放在CellInfoContainer中,通过
cellkey进行索引。
? 问题是,上述最终是从aggregation缓存中获取单元值的,但一开始缓存
中的值又从哪里来呢,这是个关键问题。原来executeBody第一次调用ExecuteStripe()时,从缓存中获得的值都只是空值,但同时会记录下所有的cellRequest信息;然后执行完executeBody后紧接着就会调用FastBatchingCellReader的另一个方法loadAggregation,该方法内部会根据刚才设定的cellRequest信息实际从数据库中读取信息并赋予这些cells。再之后会第二次执行ExecuteStripe从而获取到真正的cell值,循环往复,直至没有新的cellRequest产生为止。参见executeBody主体代码: While(true){ }
evaluator.setCellReader(fastBatchingCellReader); ExecuteStripe(evaluator);
If(!fastBatchingCellReader.loadAggregation(query))
return;//此处可以退出循环了。
? 其实上面的第一步骤或第二步骤中,在决定各轴成员值时,可能就需要获
取cell值(如排序函数),此时就已经会记录request和进行loadAggregation了;如果这样的话,到第三步时可能缓存中就已经有值可以直接获取了。
? 研发注意:在RolapEvaluator中,许多方法对性能的影响很大,这里都用
了final,以便JVM可以优化它们。如果未来需要的话,final可以移除,方法可以重写。
2.5 执行完execute后,将mondrian的result转换成JPivot的result。
4. 修改点
4.1. mondrian角色参数化
mondrian本身是支持角色的(但mondrian本身不负责角色与用户的挂钩);在实际应用中,角色往往不能事先定义死,而是可能定义出一个角色模版,但里面会有些参数;实际运行时再把这些参数实例化。例如:
在mondrian的ROlapSchema模型对象中原有一个mapNameToRoles哈希表,存储着schema中所有的role对象(不是role定义,role定义本身是另外一个对象);在mondrian的ROlapConnection对象中则有一个role对象,代表当前connection使用的角色;该角色实际上是引用schema角色组中的某个角色,schema角色组中的对象为所有连接共享。 当引入参数化需求后,由于每个连接建立时传递的参数都不一样,因此原schema中的角色对象组列表已经没有意义,原schema中的role定义信息则还需要保留下来;而connection中的role对象则每次都根据实例参数重新创建。 相应的修改之处为(查找 add by zyz之处即可): ? mondrian.rolap.RolapSchema类中,修改了createRole方法,并相应的添加了几 个辅助方法。 ? mondrian.rolap.RolapConnection类中,相应的把role的获取方法做了些修改。 ? jpivot 中,com.tonbeller.jpivot.tags.MondrianModelFactory 添加 grantParameters属性。 ? jpivot中,..MondrianOlapModelTag类中同理添加grantParameters属性,并且 增加:cfg.setGrantParameters(grantParameters)。 ? 相应的需要修改jpiovt-tags.tld,增加grantParameters属性。 4.2. 缓存失效控制 建议在jpivot.mondrian.mondrianModel类的destroy()方法中加入下面代码: mondrian.rolap.RolapSchema rschema = (mondrian.rolap.RolapSchema)monConnection.getSchema(); rschema.flushRolapStarCaches(true); 以实现在一次新的连接建立后,清理缓存。但由于缓存是全局共享的,故要注意同步问题。Mondrian已经很好地处理了同步的问题。譬如在上述destroy中会把aggregations清空,但这便不意味着其中的单个aggregation已经销毁,相反可能正在另外一个连接中被使用;但在该连接的下一个循环中就会发现aggregations已经空了,所有的aggregation又将按需重建。由于mondrian内部的求cell过程是个无限循环过程,直至发现没有什么聚合再需要求取时才退出,因此不用担心aggreations被一个连接清空了,而另外一个连接读取不到数据。 RolapConnection类,通过新增加的DriverManagerDataSourceReg类来获得 DriverManagerDataSource。 RolapSchema 的md5算法增加了连接字符串。 4.3. connection创建参数优化 ? 优化,数据库连接,直接传递DataSource ? 优化catalog,直接传递schema字符串或字符流,减少磁盘I/O操作。 ? 优化 schema 创建, 传递知识库缓存key,创建schema的时候通过缓存代理读取 MondrianDef.Schema 4.4. 维度成员排序 Mondiran已经很好地实现了基于单元值的排序,但有的场合下还需要有基于维度成员的排序。为此需要在olap schema文件的level中设置如下(见其中的ascending属性): name=\ column=\ type=\ uniqueMembers=\ ascending=\年\。其中,若ascending为false为降序,否则为升序,缺省若不设置ascending则为true。 当然,基于单元值的排序其优先级大于基于维度成员的排序。 相应的修改之处有: ? Mondrian.olap.MondrianDef类中的Level子类,添加了ascending及其相关读取和 展现代码片段。 ? mondrian.rolap.RolapLevel中也添加了ascending属性。 ? mondrian.rolap.RolapMember中对compareTo()方法进行了修改,加入了对ascending 的判断。(此处是为了在cell排序过程中,当cell相当时再按成员值排序,那么此时就会用到member的compareTo()了)。 ? mondrian.rolap.RolapStar类中的Column子类中添加了ascending及其相关读取和展 现代码片段;其中Table子类中的makeColumnForLevelExpr()中的column创建处作了相应修改。(此处没有用到) ? mondrian.rolap.SqlMemberSource类中的sqlQuery.addOrdBy()方法中的第二个参数 全部替换成level.isAscending()。--关键的,在一开始缓存成员值时就会用到。 ? mondrian.rolap.SqlTupleReader类中的sqlQuery.addOrdBy()方法中的第二个参数全 部替换成level.isAscending()。--关键的,在一开始缓存成员值时就会用到。 4.5. 对mondrian bug的修正 1、 父子维度无法展开的问题: 修改mondrian.rolap.SqlMemberSource.getMemberChildren()函数。 从jpivot.mondrian.MondrianQueryAdapter.canExpand()中作为入口进入。 2、 维度成员在crossjoin时的乱序问题 修改了内部类PositionIter,使其每次都返回新的iterator。 3、mondrian.rolap.RolapMember.getName()修改了,以支持level的列名是整形。 4.6. to-do List 1、请求解决方案里的一个cube,若其他cube涉及到的表没有,则当前cube也会失败。