众所周知,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)。
[Measures].[销售额] - [Measures].[成本]
这里也是简单说下,里面定义了两个维表和一个事实表,然后定义了几个度量,分别用求和(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 found
at mondrian.resource.MondrianResource$_Def0.ex(MondrianResource.java:969)
at mondrian.olap.Util.lookupCube(Util.java:1053)
at mondrian.olap.Query.(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,000
Row #0: -40,000
Row #1: 330,000
Row #1: -30,000
Row #2: 460,000
Row #2: 60,000
Row #3: 470,000
Row #3: 130,000
Row #4: 440,000
Row #4: 110,000
Row #5: 370,000
Row #5: -10,000
Row #6: 480,000
Row #6: 130,000
Row #7: 400,000
Row #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