Mondrian入门
来源:互联网 发布:fifa online3改键软件 编辑:程序博客网 时间:2024/05/14 22:43
众所周知,Mondrian是一个开源OLAP引擎。国内很多BI产品都会在此基础上开发。不过网上的资料比较老旧,而且给出例子多是基于jPivot表现层的,jPivot已经多年没有更新了,部署起来也比较麻烦。最新的Mondrian3.6的下载已经将jPivot移除了,如果想学习官方demo的可以去下载3.5的。
我觉得更基础轻量的例子会比较适合入门,下面我就将我最先学习Mondrian的例子分享给大家。这个Java实例将利用Mondrian提供的OLAP引擎对已建立好的数据立方体XML进行MDX查询。
多维数据建模和MDX语法,网上的资料很多,这里就不再冗述了。
首先是关系数据库的表结构(这里插一句,我用的Derby数据库,Derby数据库会为表、字段名强制加上双引号,刚开始带来了不少麻烦):
--时间维表CREATE TABLE "dim_time"("time_id" VARCHAR(50) NOT NULL,--id"time_year" VARCHAR(50),--年"time_quarter" VARCHAR(50),--季度"time_month" VARCHAR(50),--月PRIMARY KEY("time_id"));--销售点维表CREATE TABLE "dim_store"("store_id" VARCHAR(50) NOT NULL,--id"store_province" VARCHAR(50),--省"store_city" VARCHAR(50),--市"store" VARCHAR(50),--商店PRIMARY KEY("store_id"));--销售事实表CREATE TABLE "fact_sales"("sales_id" VARCHAR(50) NOT NULL,--id"store_id" VARCHAR(50) NOT NULL,--销售点"time_id" VARCHAR(50) NOT NULL,--时间"cost" INTEGER,--成本"sales" INTEGER,--销量PRIMARY KEY("sales_id"));
这是一个非常经典场景,有两个维度分别是时间和地点来描述销售情况。
然后下面是用来描述数据立方体的XML文件(sample.xml)。
<?xml version="1.0" encoding="UTF-8" ?><Schema name="Sample"><!-- 时间维度表 --><Dimension name="Time"><Hierarchy hasAll="true" allMemberName="所有时间" primaryKey="time_id"><Table name="dim_time"/><Level name="年" column="time_year" uniqueMembers="true"/><Level name="季度" column="time_quarter" uniqueMembers="false"/><Level name="月" column="time_month" uniqueMembers="false"/></Hierarchy></Dimension><!-- 商场维度表 --><Dimension name="Store"><Hierarchy hasAll="true" allMemberName="所有销售点" primaryKey="store_id"><Table name="dim_store"/><Level name="省" column="store_province" uniqueMembers="true"/><Level name="市" column="store_city" uniqueMembers="true"/><Level name="商场" column="store" uniqueMembers="true"/></Hierarchy></Dimension><!-- 一个数据立方体 --><Cube name="销售情况"><!-- 事实表 --><Table name="fact_sales"/><!-- 维表 --><DimensionUsage name="时间" source="Time" foreignKey="time_id"/><DimensionUsage name="销售点" source="Store" foreignKey="store_id"/><!-- 度量 --><Measure name="销售额" column="sales" aggregator="sum" datatype="Integer" formatString="#,##0"/><Measure name="成本" column="cost" aggregator="sum" datatype="Integer" formatString="#,##0"/><Measure name="平均销售额" column="sales" aggregator="avg" datatype="Integer" formatString="#,##0"/><Measure name="平均成本" column="cost" aggregator="avg" datatype="Integer" formatString="#,##0"/><!-- 度量(计算成员) --><CalculatedMember name="利润" dimension="Measures"><Formula>[Measures].[销售额] - [Measures].[成本]</Formula><CalculatedMemberProperty name="FORMAT_STRING" value="#,##0"/></CalculatedMember></Cube></Schema>
这里也是简单说下,里面定义了两个维表和一个事实表,然后定义了几个度量,分别用求和(sum)和平均值(avg)进行聚集,最后定义了一个计算成员(CalculatedMember),可以通过MDX片段计算出一个度量。
接下来就是Java实例了。
// 数据库连接信息,这里用的derby String dbName = "sample"; String driver = "org.apache.derby.jdbc.EmbeddedDriver"; String url = "jdbc:derby:" + dbName; String userName = "sa"; String password = "sa"; // 立方体定义文件 String xmlFile = "sample.xml"; // mdx查询 String mdxStr = "select {[Measures].[销售额],[Measures].[利润]} on columns," + "{[季度].Members} ON rows" + " from 销售情况 " + "where [商场].[支店01]"; // 建立连接 PropertyList connectInfo = new PropertyList(); connectInfo.put("Provider", "mondrian"); connectInfo.put("JdbcDrivers", driver); connectInfo.put("Jdbc", url); connectInfo.put("JdbcUser", userName); connectInfo.put("JdbcPassword", password); connectInfo.put("Catalog", xmlFile); mondrian.olap.Connection conn = mondrian.olap.DriverManager .getConnection(connectInfo, null); // 执行查询 mondrian.olap.Query query = conn.parseQuery(mdxStr); mondrian.olap.Result result = conn.execute(query); // 输出结果 PrintWriter pw = new PrintWriter(System.out); result.print(pw); pw.flush();大部分情况上面的代码可以工作良好,但是这里执行之后出现了以下错误。
Caused by: mondrian.olap.MondrianException: Mondrian Error:MDX cube '销售情况' not foundat mondrian.resource.MondrianResource$_Def0.ex(MondrianResource.java:969)at mondrian.olap.Util.lookupCube(Util.java:1053)at mondrian.olap.Query.<init>(Query.java:161)at mondrian.olap.Parser$FactoryImpl.makeQuery(Parser.java:927)at mondrian.parser.MdxParserImpl.selectStatement(MdxParserImpl.java:1241)at mondrian.parser.MdxParserImpl.statement(MdxParserImpl.java:1074)at mondrian.parser.MdxParserImpl.statementEof(MdxParserImpl.java:188)at mondrian.parser.JavaccParserValidatorImpl.parseInternal(JavaccParserValidatorImpl.java:57)at mondrian.olap.ConnectionBase.parseStatement(ConnectionBase.java:96)... 3 more只有当XML中出现中文时才会出现这样的错误,这很明显是字符编码的问题,通过log可以发现MDX已经被正常解析并没有出现乱码,由此可以推测出问题出在数据立方体XML的解析上,但是Mondrian并没有提供连接字符集的设置方法。还好它是一个开源项目,代码并不多,很快就能找到解析XML的地方,在mondrian.olap.Util.java中有这么一个方法readVirtualFileAsString,如下:
public static String readVirtualFileAsString( String catalogUrl) throws IOException { InputStream in = readVirtualFile(catalogUrl); try { final byte[] bytes = Util.readFully(in, 1024); final char[] chars = new char[bytes.length]; for (int i = 0; i < chars.length; i++) { chars[i] = (char) bytes[i]; } return new String(chars); } finally { if (in != null) { in.close(); } } }
我们可以看到它将xml文件字节流中的每一个字节转换为字符之后才构造xml字符串,难怪即使将xml的编码格式改成GBK都没辙,这遇到中文不乱码才怪了。这种处理方式我也想不通,反正老外从来也不考虑我们中国程序员的感受。
如果不想修改它的源码的话,这里有一种解决办法。创建连接本来就有两种方式,一种就是上面用到的指定xml文件的路径(Catalog),还有一种就是直接传入xml文件的内容(CatalogContent),我们用这种方式就不怕乱码了。
首先仿造上面的readVirtualFileAsString新建一个getCatalogContent方法
private static String getCatalogContent() throws Exception { InputStream inputStream = mondrian.olap.Util.readVirtualFile(xmlFile); try { final byte[] bytes = Util.readFully(inputStream, 1024); // 下面是mondrian原来的实现,由于byte被强制转化为char,汉字全为乱码,故将这段处理处理掉。 // final char[] chars = new char[bytes.length]; // for (int i = 0; i < chars.length; i++) { // chars[i] = (char) bytes[i]; // } return new String(bytes, "UTF-8"); } finally { if (inputStream != null) { inputStream.close(); } } }
然后对数据连接方式稍作修改
// 建立连接 PropertyList connectInfo = new PropertyList(); connectInfo.put("Provider", "mondrian"); connectInfo.put("JdbcDrivers", driver); connectInfo.put("Jdbc", url); connectInfo.put("JdbcUser", userName); connectInfo.put("JdbcPassword", password); // mondrian默认解析xml的方法不带字符集,中文会有乱码,故自行取得CatalogContent // connectInfo.put("Catalog", xmlFile); connectInfo.put("CatalogContent", getCatalogContent()); mondrian.olap.Connection conn = mondrian.olap.DriverManager .getConnection(connectInfo, null);
执行成功,结果如下:
Axis #0:{[销售点].[湖北省].[武汉市].[支店01]}Axis #1:{[Measures].[销售额]}{[Measures].[利润]}Axis #2:{[时间].[2012].[Q1]}{[时间].[2012].[Q2]}{[时间].[2012].[Q3]}{[时间].[2012].[Q4]}{[时间].[2013].[Q1]}{[时间].[2013].[Q2]}{[时间].[2013].[Q3]}{[时间].[2013].[Q4]}Row #0: 330,000Row #0: -40,000Row #1: 330,000Row #1: -30,000Row #2: 460,000Row #2: 60,000Row #3: 470,000Row #3: 130,000Row #4: 440,000Row #4: 110,000Row #5: 370,000Row #5: -10,000Row #6: 480,000Row #6: 130,000Row #7: 400,000Row #7: 30,000
写到这里,其实还有一个问题,上面利用到的conn.execute(query)方法其实是已经过时了的,查看doc会发现这个方法将在4.0版本后被移除,官方推荐利用olap4j的实现。olap4j是一个OLAP的API,现在已成了标准,下面就是olap4j的实现方式。
// 建立连接 String strUrl = "jdbc:mondrian:"; strUrl += "Jdbc=" + url; strUrl += ";JdbcUser=" + userName; strUrl += ";JdbcPassword=" + password; // strUrl += ";Catalog=" + xmlFile; strUrl += ";CatalogContent=" + getCatalogContent(); Class.forName("mondrian.olap4j.MondrianOlap4jDriver"); Connection olap4jConn = DriverManager.getConnection(strUrl); OlapConnection olapConn = olap4jConn.unwrap(OlapConnection.class); // 执行查询 OlapStatement statement = olapConn.createStatement(); CellSet cellSet = statement.executeOlapQuery(mdxStr); // 输出结果 CellSetFormatter formatter = new RectangularCellSetFormatter(false); formatter.format(cellSet, new PrintWriter(System.out, true));
执行结果:
| | 销售额 | 利润 |+------+----+---------+---------+| 2012 | Q1 | 330,000 | -40,000 || | Q2 | 330,000 | -30,000 || | Q3 | 460,000 | 60,000 || | Q4 | 470,000 | 130,000 || 2013 | Q1 | 440,000 | 110,000 || | Q2 | 370,000 | -10,000 || | Q3 | 480,000 | 130,000 || | Q4 | 400,000 | 30,000 |
比Mondrian的更直观点。
以上就是我刚开始学习Mondrian是制作的例子,完整工程请到此下载:http://download.csdn.net/detail/chch87/7210135
- mondrian入门
- Mondrian入门
- Mondrian入门 提取数据
- Mondrian
- Mondrian
- Mondrian
- 数据仓库研究之二--mondrian入门
- 一个JPivot+Mondrian入门的小例子
- 数据仓库研究之二--mondrian入门
- Mondrian入门介绍之schema manager
- OLAP简介及Mondrian快速入门
- OLAP简介及Mondrian快速入门
- jpivot+mondrian
- Mondrian异常
- JPivot+Mondrian
- Mondrian 简介
- Pentaho Mondrian
- saiku mondrian
- 微博该不该封杀微信
- 插入排序实现_c++
- 第六周作业
- 黑马程序员_java基础知识(四)IO流
- 2014-04-17 nand flash驱动程序__
- Mondrian入门
- Manacher’s Algorithm( O(n)最长回文串)
- 缓冲区设计--环形队列
- Android——Service 使用(二)
- 游长实参表
- 【Java Web】Jsp+Servlet+JavaBean+MySql入门级MVC实例
- Nyoj 12 喷水装置(二)
- 给你一个字符串,把连续出现的相同字符串变成只出现一次
- NoSQL以及其应用场景