使用jxl生成复杂报表的分析与设计(一)

使用jxl生成复杂报表的分析与设计(一)
    在实际项目中,特别是企业应用开发中,报表开发是其中很重要的一项功能,基本上都会要求将统计数据导出成 Excel ,不但如此,这些报表格式一般也比较复杂,尤其是显示的数据,往往都是很多业务数据综合而成的结果。大象根据自己以往做报表的经验,进行了一下总结,给刚开始做报表开发或是有需要的朋友一点借鉴。
     由于大象以前做的报表距离现在时间已经很久了,有些相关的业务数据已经找不到,再加上这些数据涉及商业机密也不便公开,所以本篇主要着重于阐述解决问题的思路,而不是去关注代码实现,当然代码肯定会有,我主要想告诉大家怎么去解决这类问题。同时大象将在第二篇文章中,把自己遇到过的一些比较复杂的报表案例拿出来进行分析,用抽丝剥茧的方式,让报表开发变得不再那么让人无处下手,最多只是麻烦点而已。 ^_^
     目前 Java 开源报表比较有代表性的有 JasperReports Cewolf iReport JfreeChart 等等 技术,它们大都是用来生成不同格式或图形化的报表。而本篇文章要讲的 jxl ,则是 Java Excel API ,它是 一个成熟开源的 Excel 电子表格读取,修改,写入的项目。我们可以利用它简单,便利的 API 生成我们想要的 Excel 数据报表。
     除了 jxl 外,还有一个 apache poi 开源项目,它的功能非常强大,不仅可以操作 Excel ,还可以支持 Word PowerPoint 等等格式, 3.5 之后的版本,还加入了对 Excel 2007 以后版本的支持。
     当然 jxl 也有不足的地方,最主要体现在,它支持生成的报表最大行数为 65535 ,如果超过这个数量,将会产生异常,而且也不支持 Excel 2007+ 版本。如果平时报表数据量不是很大的话,而且只是用来生成 Excel 格式的文件,大象建议还是用 jxl 比较好,它使用简单,性能也还不错。
     根据大象的经验,定义一个报表模板,然后往里面填充数据,是开发复杂报表的一个比较不错的方法。大象将报表分为基本报表和复杂报表,所谓基本报表就是没有复合表头,每行一条数据,并且提供的数据不需要查询两张以上的表。而复杂报表正好与之相反。不管是基本报表还是复杂报表,代码有很多都是一样的,不一样的只是由业务复杂度产生的报表复杂度。
     我们先来看看用 jxl 生成 Excel 报表的代码模板:
    
     上面这个是 Cell( 单元格 ) 的通用格式化设置, WritableFont 构造函数的参数第 1 个是字体,第 2 个是字体大小,第 3 个是字体样式,第 4 false 指的是正常显示,默认为 italic 倾斜显示,第 5 个下划线样式,第 6 个指定字体颜色。设置完字体后,再设置单元格的格式,对于一般的单元格,不用设置背景色,除非报表显示需要。金额及百分比的显示要用到 NumberFormat 类,这里的格式指定为 0.00 而不是 #.## 是为了强制保留两位小数,对于这类数字一般在报表中都是设置为右对齐,这样出来的报表比较好看。当然最终还是要根据客户的要求来决定样式。
    
     使用模板方式生成 Excel 除了已经定义好表头外,还有一个好处是可以预先定义 固定列 的宽度,这样就免去了用代码来设置列宽,如果是复杂表头,也省去了不少设置表头和合并单元格的代码。请注意我前面用红字加粗的固定列三个字,因为报表的表头不一定就是模板中设置的那样,还有一种情况就是根据查询条件的年和月,动态生成表头,这些表头被排列在固定列的后面,报表生成后,这些列下面都有相应的数据。关于这部分内容我会在第二篇文章中介绍。 上图中,红框的部分就是使用模板生成 Excel 的关键代码, ResourceUtils org.springframework.util 包中的工具类。
    
     上图中的红框部分与使用模板方式相比是不是点些细微的差别?请一定注意这些细节部分的不同。 完全 jxl API 创建 Excel 所有内容都需要用代码来实现:
    
     设置标题及表头并定义每列的宽度, setColumnView 方法的第 1 个参数是列的索引,从 0 开始,第 2 个参数是宽度,这个数值既不是像素也不是长度,而是指字符数,比如第 1 列的宽度可以理解为相当于 20 个字符的宽度。以 10pt 的字体大小来算,实际上最多只能填充 19 个字符,而汉字最多 10 个,其实主要还是需要通过测试来调整最终效果。
     接下来就是填充数据,这块代码不管是用模板方式还是 API 方式都是一样的。  
    
     对于简单的单行表头,填充数据很简单,将查询结果填充到单元格中。前面定义了标题和表头,已经占去了两行,所以应该从第 3 行开始添加数据,行的索引也是从 0 开始, 2 表示第 3 行。如果是数字型的 Cell 需要用 jxl.write.Number 来进行封装,这样生成的数据才不会因为可能过大或别的因素而被 Excel 自动转化成科学计数法。如果还是想用 Lable 来封装数字,可以使用 java.text. NumberFormat 来做 格式化。最后执行关闭操作。
看到这里,有没有感觉用 jxl 做报表非常简单?可以说是,也可以说不是。因为这里面最麻烦的地方在于取得业务数据,另外对于复杂报表, for 循环里面还会包含很多条件判断、其它 for 循环以及合并行或列等操作。
     业务数据的获得有两种方式,第 1 种是按报表的样式建立一个对象模型,将要导出的数据全部填充到这个模型中,然后根据报表的格式添加数据。第 2 种是通过 SQL 语句,使用尽量少的查询次数将所有结果查询出来,填充到数据模型中。不管是第 1 种还是第 2 种最典型的就是简单表头,单表查询,所有的数据都在 Model 对象里面,执行一个循环操作就可以搞定了。但如果是复杂报表,第 1 种做法就不可取了,因为当你将这个数据对象的 List 实现后就会发现,代码之复杂,效率之低下 ( 并不是指速度慢,而是指 SQL 查询次数非常多 ) 简直令人发指。而如果采取第 2 种做法, SQL 语句无疑会很复杂 ( 不考虑视图 ) ,会有很多关联查询,如果作了分表分库的设计,那这个方式就彻底完蛋了。
     既然这两种都不是好的解决办法,那么究竟还有没有更好的解决方案呢?肯定是有的,就是分而治之,按照一种规则 ( 比如时间 ) ,提取出与报表相关的最基础数据至 Table1 中,然后对该 Table1 再进行一次处理合并至 Table2 中,提取 Table2 Table3, 如果有必要还可以对 Table3 再处理一次到 Table4 ,从 Table1 Table4 每个表里面都是一种报表数据,如果按时间分组,假定 Table1 是按小时, Table2 按天, Table3 按月, Table4 按年,那么相应的报表查询就可以做到只对一张表进行操作,而且报表需要的业务数据都已经存在该表中。我所举的这个方法是比业务较简单的情况,实际开发中要根据当前的业务来设计,因为有些项目的业务确实是非常的复杂,并不像我所说的这么简单。
     大家一定不要过于迷信这种方式,世上是没有完美解决方案的,也即所谓的没有“银弹”,这样的处理方式对大部分报表还是适用的。但如果要是遇到所统计的业务基础数据发生了变化,或者本身业务出现变更,甚至报表都发生变更,那么之前所获得的这些数据就没有什么意义了。因此,报表的实现方案一定要根据实现情况来分析,不要总想着有什么通用的解决办法,一招鲜吃遍天这样的想法是非常不可取的,软件最不缺的就是变化,不要抗拒变化,因为你根本无法阻止它,所以你得拥抱变化,享受变化。  
     本篇主要介绍了使用 jxl 生成报表的代码模板以及获取报表数据的一种处理方式,下一篇将通过两个复杂报表案例的分析,来告诉大家如何进行实现。
     本文为菠萝大象原创,如要转载请注明出处。 http://www.blogjava.net/bolo

你可能感兴趣的:(使用jxl生成复杂报表的分析与设计(一))