文章目录
一、丑话说在前头
网上搜一个案例或者资料,几乎都是一模一样的,很烦人。
而且版本很老了。出错也是很正常的,这里为了方便更多使用mondrian的朋友学习。我就在他们的基础上进行重新梳理了一下,然后来写的这篇文章。
网上几乎所有的资料都是使用的Oracle
,而我是想使用Mysql
数据库的,所以在这个地方也出现了很多问题,一同进行梳理,并记录下来。
本案例使用的Mondrian+Mysql版本的;
如果您想使用Oracle可以看这篇文章:点我查看,但是遇到的错误真的是无穷大
。想起来就头疼。
但是我希望您看我这篇文章,因为配置是一样的,只需要注意某个地方就好了,我都会一一说明的。
如果有比较重要的地方我会在之前放上下面这个图:
成果:图太多,就放一个了
通过本教程,您将了解到什么是Mondiran,及如何将mondrian支持添加到您的Java Web项目中。
在阅读本教程之前,您可能需要掌握以下概念:
-
OLAP(联机分析处理On-Line Analytical Processing),您可以通过阅读ROLAP的概念来了解OLAP;
-
MDX多维表达式,您可以通过阅读MDX的基本语法及概念来了解MDX;
二、Mondrian是什么?
Mondrian是一个开源项目。
一个用Java写成的OLAP引擎。
它用MDX语言实现查询,从关系数据库(RDBMS)中读取数据。
然后经过Java API以多维的方式对结果进行展示。
Mondrian的使用方式同JDBC驱动类似。可以非常方便的与现有的Web项目集成
(一)Mondrian的体系结构(Architecture)
Mondrian OLAP 系统由四个层组成; 从最终用户到数据中心, 顺序为:
- 1 表现层(the presentation layer)
- 2 维度层(the dimensional layer)
- 3 集合层(the star layer)
- 4 存储层(the storage layer)
结构图如下:
1、表现层(the presentation layer)
- 表现层决定了最终用户将在他们的显示器上看到什么, 及他们如何同系统产生交互。
- 有许多方法可以用来向用户显示多维数据集, 有 pivot 表 (一种交互式的表), pie, line 和图表(bar charts)。它们可以用Swing 或 JSP来实现。
- 表现层以多维"文法(grammar)(维、度量、单元)”的形式发出查询,然后OLAP服务器返回结果。
下面将对mondrian在 java web端使用的jpivot表现层进行简单介绍
(1)jpivot表现层
JPivot 是Mondrian的表现层TagLib,一直保持着良好的开发进度。
可以通过访问jpivot的官方网站http://jpivot.sourceforge.net/以获得更多的帮助及支持
jpivot使用XML/ XSLT渲染OLAP报表:
JPivot 使用 WCF (Web Component Framework) ,基于XML/XSLT来渲染Web UI组件。这使它显得十分另类。
不过,OLAP报表这种非常复杂但又有规律可循的东西,最适合使用XSLT来渲染。
jpivot完全基于JSP+TagLib:
- JPivot另外一个可能使人不惯的地方是它完全基于taglib而不是大家熟悉的MVC模式。
- 但它可以很方便的将多维数据展示给最终用户,如下表格:
- jpivot其实是一个自定义jsp的标签库。它基于XML/XSLT配置来生成相应的html。所幸的是,我们并不需要了解太多关于这方面的内容,我们只要掌握相应jsp标签的使用即可。
- 在本教程的实例中,我们将会对一些常用到的jpivot标签进行讲解。
- 您还可以通过汉化WEB-INF/jpivot下的xml文件来完成对jpivot的汉化工作
2、维度层(the dimensional layer)
- 维度层用来解析、验证和执行MDX查询要求。
- 一个MDX查询要通过几个阶段来完成:首先是计算坐标轴(axes),再者计算坐标轴axes 中cell的值。
- 为了提高效率,维度层把要求查询的单元成批发送到集合层,查询转换器接受操作现有查询的请求,而不是对每个请求都建立一个MDX 声明。
3、集合层(the star layer)
- 集合层负责维护和创建集合缓存,一个集合是在内存中缓存一组单元值, 这些单元值由一组维的值来确定。
- 维度层对这些单元发出查询请求,如果所查询的单元值不在缓存中,则集合管理器(aggregation manager)会向存储层发出查询请求
4、存储层(the storage layer)
- 存储层是一个关系型数据库(RDBMS)。它负责创建集合的单元数据,和提供维表的成员。
(二)API介绍
-
Mondrian 为客户端提供一个用于查询的API
-
因为到目前为止,并没有一个通用的用于OLAP查询的API,因此Mondrian提供了它私有的API.
-
尽管如此,一个常使用JDBC的人将同样发现它很熟悉.不同之处仅在于它使用的是MDX查询语言,而非SQL
-
下面的java片段展示了如何连接到Mondrian,然后执行一个查询,最后打印结果.
import mondrian.olap.*;
import java.io.PrintWriter;
Connection connection = DriverManager.getConnection("Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart;Catalog=/WEB-INF/FoodMart.xml;",null,false);
Query query = connection.parseQuery("SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns,{[Product].children} on rows FROM [Sales] WHERE ([Time].[1997].[Q1], [Store].[CA].[San Francisco])");
Result result = connection.execute(query);
result.print(new PrintWriter(System.out));
- 与JDBC类似,一个Connection由DriverManager创建,
Query 对象类似于JDBC 的Statement,它通过传递一个MDX语句来创建.Result对象类似于JDBC的ResultSet,只不过它里面保存的是多维数据 - 可以通过查看Mondrian帮助文档里的javadoc来获取更多关于Mondrian API的资料
通过上面的介绍,您应该对mondrian的体系有一个基本的了解。
下面通过一个简单的例子来加深理解吧。
三、一个简单的Mondrian例子
-
现在让我们用一个简单的例子来说明将Mondrian支持添加到您java web的具体步骤。
-
本测试需要的环境:
操作系统:Windows 2000;
Web服务器:tomcat6.0;
关系数据库:sql server 2000;
开发工具:eclipse + myeclipse;
JDBC驱动:jtds-1.2.2;您可以在http://tomcat.apache.org/上下载到tomcat的最新版本及帮助;
您可以在http://www.myeclipseide.com/上下载到myeclipse的最新版本及相应的eclipse开发平台版本
注意
注意:下面这就是个比较坑的地方了,因为版本都太低了,本次我使用的是:
操作系统:Windows 10;
Web服务器:tomcat 8.5;
关系数据库:Mysql 5.7;
开发工具:eclipse或idea;
JDBC驱动:mysql-connector-java-5.1.48;
相信大家几乎都是和我一样
(一)准备Mondrian资源
从http://sourceforge.net/projects/mondrian/下载Mondrian的最新版本(目前版本为3.0,大约有50M+大小)。
这个地方需要注意了
因为这个网站好老了,资料出来的时候是2014年,现在是2020年,已经6年了。所以如果再按照上面的下载方法的话,就会无法下载。即使能下载也是不对的。
我给大家准备好了,地址在资料找全来之不易,感谢理解
:
https://download.csdn.net/download/qq_17623363/12322589
(二)创建项目
1、启动eclipse。
2、在eclipse中新创建一个动态web项目,名为Tezza。
具体步骤如下:
File—>New—>Dynamic Web Project
项目名不是必须的,可以随意;
选择好tomcat
一个新javaweb项目的文件结构如下:
(三)添加必要的文件
解压:mondrian.war
进入解压后的文件夹,选中jpivot、wcf二个文件夹及busy.jsp、error.jsp、testpage.jsp三个文件,我们需要将这些资源复制到我们测试项目的WebRoot文件夹中。按ctrl+C键复制。
注:jpivot、wcf这两个文件夹包含mondrian使用的图像和css文件。Busy.jsp显示等
待页面、error.jsp显示出错页面、testpage.jsp这文件的用处将在后面介绍。
切换到eclipse界面,在我们的Tezzb项目的WebRoot文件夹处右击鼠标,在弹出的菜单中选择Paste(粘贴)即可,或者直接快捷键粘贴也可以。
粘贴完成后的项目结构如下:
最后进入WEB-INF文件夹(在上面步骤中解压的项目文件mondrian.war里),选中jpivot、lib、wcf这三个文件夹,同样需要复制它们到测试项目的WEB-INF文件夹中。
Jpivot、wcf这两个文件夹包含jpivot和wcf用于生成用户界面的配置文件(.xml、.xsl)及标签文件(*.tld)的定义。Lib文件夹包含的是mondrian所要用的java包。
复制到:
可以看到还是有错误,不要紧,继续跟着我来:
解压我给的jakarta-taglibs-standard-1.1.2.zip
把下面这俩拷贝到eclipse工程的lib下:
然后把c.tld拷贝到WEB-INF下
然后自动就没错误了:
(四)配置web.xml
用记事本或者其他软件打开我们在上面解压的项目的WEB-INF/web.xml文件
我们需要从该web.xml文件中抽取出所需要的配置。
1、过滤器(filter)
右图下面所示的xml代码到我们测试项目Tezz的web.xml文件中。
<filter>
<filter-name>JPivotController</filter-name>
<filter-class>com.tonbeller.wcf.controller.RequestFilter</filter-class>
<init-param>
<param-name>indexJSP</param-name>
<param-value>/index.html</param-value>
<description>如果这是一个新的会话,则转到此页面</description>
</init-param>
<init-param>
<param-name>errorJSP</param-name>
<param-value>/error.jsp</param-value>
<description>出错时显示的页面</description>
</init-param>
<init-param>
<param-name>busyJSP</param-name>
<param-value>/busy.jsp</param-value>
<description>这个页面用于当用户点击一个查询时,在这个查询还未将结果还回给用户时所显示的界面</description>
</init-param>
</filter>
<filter-mapping>
<filter-name>JPivotController</filter-name>
<url-pattern>/testpage.jsp</url-pattern>
</filter-mapping>
作用:
这个过滤器在访问/testpag e.jsp前被调用。它被设计成jpivot的前端控制器,用于判断并将用户的请求发送到某个页面。
注:
在实际项目中可以使用您自己定 义的servlet或使用其他技术来替代它以提供更多的功能
2、资源初始化
复制下面的listener到我们的web.xml文件中(用于初始化一些资源)
<listener>
<listener-class>mondrian.web.taglib.Listener</listener-class>
</listener>
<!-- 资源初始化 -->
<listener>
<listener-class>com.tonbeller.tbutils.res.ResourcesFactoryContextListener</listener-class>
</listener>
3、将数据生成Excel或者pdf文件
Print servlet
,该servlet用于将数据生成Excel文件或pdf文件并返回给用户,如果您需要用到该功能,则需要将其copy到您项目的web.xml文件中
<servlet>
<servlet-name>Print</servlet-name>
<display-name>Print</display-name>
<description>Default configuration created for servlet.</description>
<servlet-class>com.tonbeller.jpivot.print.PrintServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Print</servlet-name>
<url-pattern>/Print</url-pattern>
</servlet-mapping>
4、接受并执行一个MDX查询功能
- MDXQueryServlet用于接受并执行一个MDX查询,然后将该查询以Html表格的形式返回。其中的参数connectString用于指定连接到数据库的字符串,例如使用jtds驱动连接到sql server 2000的字符串如下:
Provider=mondrian;Jdbc=jdbc:jtds:sqlserver://localhost/Tezz;user=sa;password=123456;Catalog=/WEB-INF/queries/tezz.xml;JdbcDrivers=net.sourceforge.jtds.jdbc.Driver;
- 如果您需要用到该功能,则需要将其copy到您项目的web.xml文件中。
<servlet>
<servlet-name>MDXQueryServlet</servlet-name>
<servlet-class>mondrian.web.servlet.MDXQueryServlet</servlet-class>
<init-param>
<param-name>connectString</param-name>
<param-value>@mondrian.webapp.connectString@</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MDXQueryServlet</servlet-name>
<url-pattern>/mdxquery</url-pattern>
</servlet-mapping>
5、生成图表功能
DisplayChart
和GetChart
这两个Servlet 用于生成图表和将其显示给最终用户,如果您需要用到该功能,则需要将其copy到您项目的web.xml文件中。
<!-- jfreechart provided servlet -->
<servlet>
<servlet-name>DisplayChart</servlet-name>
<servlet-class>org.jfree.chart.servlet.DisplayChart</servlet-class>
</servlet>
<!-- jfreechart provided servlet -->
<servlet>
<servlet-name>GetChart</servlet-name>
<display-name>GetChart</display-name>
<description>Default configuration created for servlet.</description>
<servlet-class>com.tonbeller.jpivot.chart.GetChart</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DisplayChart</servlet-name>
<url-pattern>/DisplayChart</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>GetChart</servlet-name>
<url-pattern>/GetChart</url-pattern>
</servlet-mapping>
- 它们用于向用户生成和显示如下所示的各种图表:
6、添加标签库
最后添加以下标签库到我们的web.xml项目中即可
这里需要注意了,如果你直接把下面这些复制到web.xml,我敢打赌,你一定一定会报错。我也没弄清楚,所以我也不敢说,只能大胆的猜测是版本的问题
<taglib>
<taglib-uri>http://www.tonbeller.com/wcf</taglib-uri>
<taglib-location>/WEB-INF/wcf/wcf-tags.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://www.tonbeller.com/jpivot</taglib-uri>
<taglib-location>/WEB-INF/jpivot/jpivot-tags.tld</taglib-location>
</taglib>
解决办法:
<jsp-config>
<taglib>
<taglib-uri>http://www.tonbeller.com/wcf</taglib-uri>
<taglib-location>/WEB-INF/wcf/wcf-tags.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://www.tonbeller.com/jpivot</taglib-uri>
<taglib-location>/WEB-INF/jpivot/jpivot-tags.tld</taglib-location>
</taglib>
</jsp-config>
到这里,您应该对mondrian在web.xml的配置有一定的了解,并可按需要添加相应的功能。
接下来我们将要创建本例子所要用到的表格及数据。
(五)准备测试数据表
本例使用的表结构如下所示:
Sale是事实表,它有两个维:客户(customer)维和由两个表组成的产品(Product)维。
表格的创建很简单,您只需要将下面的sql语句导入数据库即可
/**销售表*/
create table Sale (
saleId int not null,
proId int null,
cusId int null,
unitPrice float null, /*单价 */
number int null, /*数量 */
constraint PK_SALE primary key (saleId)
)
/**用户表*/
create table Customer (
cusId int not null,
gender char(1) null, /*性别 */
constraint PK_CUSTOMER primary key (cusId)
)
/**产品表*/
create table Product (
proId int not null,
proTypeId int null,
proName varchar(32) null,
constraint PK_PRODUCT primary key (proId)
)
/**产品类别表*/
create table ProductType (
proTypeId int not null,
proTypeName varchar(32) null,
constraint PK_PRODUCTTYPE primary key (proTypeId)
)
使用以下sql语句导入数据
insert into Customer(cusId,gender) values(1,'F');
insert into Customer(cusId,gender) values(2,'M') ;
insert into Customer(cusId,gender) values(3,'M') ;
insert into Customer(cusId,gender) values(4,'F') ;
insert into producttype(proTypeId,proTypeName) values(1,'电器') ;
insert into producttype(proTypeId,proTypeName) values(2,'数码') ;
insert into producttype(proTypeId,proTypeName) values(3,'家具') ;
insert into product(proId,proTypeId,proName) values(1,1,'洗衣机') ;
insert into product(proId,proTypeId,proName) values(2,1,'电视机') ;
insert into product(proId,proTypeId,proName) values(3,2,'mp3') ;
insert into product(proId,proTypeId,proName) values(4,2,'mp4') ;
insert into product(proId,proTypeId,proName) values(5,2,'数码相机') ;
insert into product(proId,proTypeId,proName) values(6,3,'椅子') ;
insert into product(proId,proTypeId,proName) values(7,3,'桌子') ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(1,1,1,340.34,2) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(2,1,2,140.34,1) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(3,2,3,240.34,3) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(4,3,4,540.34,4) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(5,4,1,80.34,5) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(6,5,2,90.34,26) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(7,6,3,140.34,7) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(8,7,4,640.34,28) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(9,6,1,140.34,29) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(10,7,2,740.34,29) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(11,5,3,30.34,28) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(12,4,4,1240.34,72) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(13,3,1,314.34,27) ;
insert into sale(saleId,proId,cusId,unitPrice,number) values(14,3,2,45.34,27) ;
(六) 建立模式(schema)文件
- 一个模式定义了一个多维数据库. 它包含一个逻辑模型(logical model)、一组数据立方(consisting of cubes)、层次(hierarchies)、和成员(members), 并映射到物理模型(关系数据库)上。
- 简单的说,配置一个模式就是配置一个关系数据结构到多维数据结构的映射。
注: 关于mondrian的模式及模式的配置,您可以通过阅读mondrian的基本模式.pptx来了解。这里我们只对其进行了简单介绍。
1、创建模式文件
模式文件的创建很简单。首先在WEB-INF下新建一个queries的文件夹,然后在该文件夹下创建一个名为tezz.xml的文件。再按下面的步骤将xml元素添加入即可。
2、配置模式文件
(1)添加数据立方Sales
往tezz.xml中添加如下代码:
<?xml version="1.0" encoding="UTF-8"?>
<Schema name="tezz">
<Cube name="Sales">
<!-- 事实表sale(fact table) -->
<Table name="sale" />
</Cube>
</Schema>
解释:
(2)添加数据立方Sales的维
添加客户维:
<!-- 客户维 -->
<Dimension name="客户性别" foreignKey="cusId">
<Hierarchy hasAll="true" allMemberName="所有性别" primaryKey="cusId">
<Table name="Customer"></Table>
<Level name="gender" column="gender"></Level>
</Hierarchy>
</Dimension>
解释:
(3)添加产品维
因为产品维由两个表连接而成,因此比客户维复杂些
<!-- 产品类别维 -->
<Dimension name="产品类别" foreignKey="proId">
<Hierarchy hasAll="true" allMemberName="所有产品" primaryKey="proId" primaryKeyTable="product">
<join leftKey="proTypeId" rightKey="proTypeId">
<Table name="product" />
<Table name="producttype"></Table>
</join>
<Level name="proTypeId" column="proTypeId"
nameColumn="proTypeName" uniqueMembers="true" table="producttype" />
<Level name="proId" column="proId" nameColumn="proName"
uniqueMembers="true" table="product" />
</Hierarchy>
</Dimension>
将上面的代码复制在客户维的下面即可
解释:
(4)添加度量
共有三个度量:
数量
、平均单价
和总销售额
<!-- 产品数量度量 -->
<Measure name="数量" column="number" aggregator="sum" datatype="Numeric" />
<Measure name="总销售额" aggregator="sum" formatString="¥#,##0.00">
<!-- unitPrice*number所得值的列 -->
<MeasureExpression>
<SQL dialect="generic">(unitPrice*number)</SQL>
</MeasureExpression>
</Measure>
<CalculatedMember name="平均单价" dimension="Measures">
<Formula>[Measures].[总销售额] / [Measures].[数量]</Formula>
<CalculatedMemberProperty name="FORMAT_STRING" value="¥#,##0.00" />
</CalculatedMember>
将这些xml元素添加在维元素下面即可
解释:
(5)最后生成的tezz.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Schema name="tezz">
<Cube name="Sales">
<!-- 事实表(fact table) -->
<Table name="sale" />
<!-- 客户维 -->
<Dimension name="客户性别" foreignKey="cusId">
<Hierarchy hasAll="true" allMemberName="所有性别" primaryKey="cusId">
<Table name="Customer"></Table>
<Level name="gender" column="gender"></Level>
</Hierarchy>
</Dimension>
<!-- 产品类别维 -->
<Dimension name="产品类别" foreignKey="proId">
<Hierarchy hasAll="true" allMemberName="所有产品" primaryKey="proId" primaryKeyTable="product">
<join leftKey="proTypeId" rightKey="proTypeId">
<Table name="product" />
<Table name="producttype"></Table>
</join>
<Level name="proTypeId" column="proTypeId"
nameColumn="proTypeName" uniqueMembers="true" table="producttype" />
<Level name="proId" column="proId" nameColumn="proName"
uniqueMembers="true" table="product" />
</Hierarchy>
</Dimension>
<Measure name="数量" column="number" aggregator="sum" datatype="Numeric" />
<Measure name="总销售额" aggregator="sum" formatString="¥#,##0.00">
<!-- unitPrice*number所得值的列 -->
<MeasureExpression>
<SQL dialect="generic">(unitPrice*number)</SQL>
</MeasureExpression>
</Measure>
<CalculatedMember name="平均单价" dimension="Measures">
<Formula>[Measures].[总销售额] / [Measures].[数量]</Formula>
<CalculatedMemberProperty name="FORMAT_STRING" value="¥#,##0.00" />
</CalculatedMember>
</Cube>
</Schema>
(七)编写MDX查询语句
1、MDX概念
- 在模式文件定义完成之后,我们就可以根据它来编写相应MDX查询语句了。
- 本例所用的MDX语句如下:
select
{[Measures].[数量],[Measures].[平均单价],[Measures].[总销售额]} on columns,
{([产品类别].[所有产品],[客户性别].[所有性别])}
on rows
from [Sales]
解释:
您应该可以很清楚的看出,columns轴维度包含的是度量。Rows轴维度包含的是维度 中的层次的所有成员(allMemberName)
2、创建查询文件
- 现在我们将创建一个jsp文件,该jsp使用jpivot的mondrianQuery标签来完成查询。
- 该文件最后将被testpage.jsp使用。
- 在/WEB-INF/queries文件夹下面创建一名为Testb的jsp文件。该jsp包含如下内容:
<%@ page session="true" contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://www.tonbeller.com/jpivot" prefix="jp" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<jp:mondrianQuery id="query01"
catalogUri="/WEB-INF/queries/tezz.xml"
jdbcDriver="net.sourceforge.jtds.jdbc.Driver"
jdbcUrl="jdbc:jtds:sqlserver://localhost:2720/Tezz;user=sa;password=123456">
select {[Measures].[数量],[Measures].[平均单价],[Measures].[总销售额]} ON columns,
{([产品类别].[所有产品],[客户性别].[所有性别])} ON rows
from [Sales]
</jp:mondrianQuery>
<c:set var="title01" scope="session">Sales</c:set>
解释:
注意:
我在配置的是<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
出现了问题,如果你也出现了可以替换成备用core库:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
(八)运行项目
启动Tomcat;
在浏览器上输入http://localhost:8080/Tezzb/testpage.jsp?query=tezz即可看到如下结果:
注:
testpage.jsp?query=tezz,这里的tezz即刚我们创建的用于查询jsp文件名称