您好,欢迎访问一九零五行业门户网

怎么使用Java工具类实现高效编写报表

为什么使用java代码写报表对于报表数据大部分情况下使用写sql的方式为大屏/报表提供数据来源,但是对于某些复杂情况下仅仅使用sql无法实现,或者实现起来困难的时候,会采取通过代码实现复杂的逻辑最终将结果返回。
遇到的问题对于相对复杂的报表,经常需要做数据的连接即表与表的join,分组,计算等操作。sql天然支持这些操作,实现起来很轻松。但是当我们在java代码中需要对数据进行连接时,原生支持的就并不那么友好,我们常常会这么实现
现在有两个集合
list<contractdetail> contractdetails; // 合同明细集合,合同会重复list<contractinfo> contractinfos; // 合同主要信息,不会有重复合同
对应数据结构
public class contractdetail { /** * 合同编号 */ private string contractno; /** * 总金额 */ private bigdecimal moneytotal;}public class contractinfo { /** * 合同编号 */ private string contractno; /** * 状态 */ private string status;}
需求
contractdetails 根据 contractno关联 contractinfos,过滤出status = '已签订&rsquo;的数据
再根据 contractdetails 中的contractno分组,分别求每个 contractno对应的moneytotal之和
最终输出的应该为一个map
map<string /* 合同编码 */, bigdecimal /* 对应moneytotal之和 */> result;
通常我们会这么实现
// setp 1 过滤出 已签订状态的合同编码set<string> stopcontract = contractinfos.stream() .filter(it -> "已签订".equals(it.getstatus())) .map(contractinfo::getcontractno).collect(collectors.toset());//step2 根据 step1的合同编码集合过滤出状态正确的contractdetail contractdetails = contractdetails.stream() .filter(it -> stopcontract.contains(it.getcontractno())) .collect(collectors.tolist());//step3 根据contractno分别累加对应的moneytotal map<string, bigdecimal> result = new hashmap<>(); contractdetails.stream().foreach(it -> { bigdecimal moneytotal = optional.ofnullable(result.get(it.getcontractno())) .orelse(bigdecimal.zero); moneytotal = moneytotal.add(it.getmoneytotal() != null ? it.getmoneytotal() : bigdecimal.zero); result.put(it.getcontractno(), moneytotal); });
显然这个实现时比较复杂的,因为使用sql的话无非就是 join 连接之后加上group by分组。求和。就可以轻易解决这个问题。那么看看后面这个工具类,再思考有没有更简单的办法实现。
工具类collectiondatastream集合数据流collectiondatastream的功能是通过接口对集合之间做关联,实现了类似sql join和left join两个操作
并且实现和java中的stream相互转换的功能。
聚合数据结构将集合转换成类似表结构的数据结构,包含表名,数据
public class aggregationdata { map<string, map> aggregationmap; private aggregationdata(){ aggregationmap = new hashmap<>(); } //key 为别名,value为对应对象 public aggregationdata(string tablename, object data) { aggregationmap = new hashmap<>(); aggregationmap.put(tablename, beanutil.beantomap(data)); } public map<string, map> getrowalldata() { return aggregationmap; } public map gettabledata(string tablename) { if (!aggregationmap.containskey(tablename)) { throw new datastreamexception(tablename + ".not.exists"); } return aggregationmap.get(tablename); } public void settabledata(string tablename, object data) { if(aggregationmap.containskey(tablename)){ throw new datastreamexception(tablename+".has.been.exists!"); } aggregationmap.put(tablename, beanutil.beantomap(data)); } private void settabledata(string tablename, map<string, object> data) { map<string, object> tabledata = optional.ofnullable(aggregationmap.get(tablename)).orelse(new hashmap<string, object>()); tabledata.putall(data); aggregationmap.put(tablename, tabledata); } public aggregationdata copyaggregationdata() { aggregationdata aggregationdata = new aggregationdata(); for (string tablename : this.getrowalldata().keyset()) { aggregationdata.settabledata(tablename, this.getrowalldata().get(tablename)); } return aggregationdata; }}
aggregationdata代表一行数据,aggregationmap的key为表名,value为对应的数据
来详细看看这个接口
import java.util.collection;import java.util.map;import java.util.function.function;import java.util.stream.stream;public interface collectiondatastream<t> { /** *将集合转化为数据流,并给一个别名 * @param tablename * @param collection * @return */ static collectiondatastream<aggregationdata> of(string tablename, collection<?> collection) { return new collectiondatastreamimpl(tablename, collection); } /** *将 stream转化为数据流,并给一个别名 * @param tablename * @param collection * @return */ static collectiondatastream<aggregationdata> of(string tablename, stream<?> collection) { return new collectiondatastreamimpl(tablename, collection); } /** * 内连接,可自定义连接条件,使用双循环 * * @param tablename * @param collection * @param predict * @param <t1> * @return */ <t1> collectiondatastream<t> join(string tablename, collection<t1> collection, joinpredicate<t, t1> predict); /** * 等值内连接,使用map优化 * * @param collection * @param tablename * @param aggregationmapper * @param datavaluemapper * @param <t1> * @param <r> * @return */ //等值条件推荐用法 <t1, r> collectiondatastream<t> joinusehashonequalcondition(string tablename, collection<t1> collection, function<t, r> aggregationmapper, function<t1, r> datavaluemapper); /** * 左连接,可自定义连接条件,使用双循环 * * @param tablename * @param collection * @param predict * @param <t1> * @return */ <t1> collectiondatastream<t> leftjoin(string tablename, collection<t1> collection, joinpredicate<t, t1> predict); /** * 等值左连接,使用map优化 * * @param collection * @param tablename * @param aggregationmapper * @param datavaluemapper * @param <t1> * @param <r> * @return */ <t1, r> collectiondatastream<t> leftjoinusehashonequalcondition( string tablename, collection<t1> collection,function<t, r> aggregationmapper, function<t1, r> datavaluemapper); stream<t> tostream(); stream<map> tostream(string tablename); <r> stream<r> tostream(string tablename, class<r> clzz); <r> stream<r> tostream(function<aggregationdata, r> mapper);}
注意joinusehashonequalcondition和join两个方法的区别。
如果集合之间的连接时某个字段等值连接,那么使用joinusehashonequalcondition,其内部使用的是map分组之后进行连接。而直接使用join的话连接条件可自定义,但是是通过双重循环进行条件判断,效率较低。因此等值情况下,使用joinusehashonequalcondition效率更高。
如何使用还是已上面的需求为例
先进行两个集合之间的连接
collectiondatastream.of("t1", contractdetails) .joinusehashonequalcondition( contractinfos.stream().filter(it -> "已签订".equals(it.getstatus())).collect(collectors.tolist()), "t2", agg -> agg.gettabledata("t1").get("contractno"), contractinfo::getcontractno );
代码解析
collectiondatastream.of("t1", contractdetails)
是将集合contractdetails转换为表名为t1的数据流,
.joinusehashonequalcondition( contractinfos.stream().filter( "t2", it -> "已签订".equals(it.getstatus())).collect(collectors.tolist()), agg -> agg.gettabledata("t1").get("contractno"), contractinfo::getcontractno );
内连接contractinfos,同时给contractinfos起别名t2,连接条件是等值连接 t1的contractno和contractinfos的contractnol连接之后得到新的聚合数据流
当然也可以使用自定义的连接实现
collectiondatastream.of("t1", contractdetails) .join("t2", contractinfos.stream().filter(it -> "已签订".equals(it.getstatus())).collect(collectors.tolist()), (agg, data) -> agg.gettabledata("t1").get("contractno").equals(data.getcontractno()) )
这里通过内连接,那么也起到了一个过滤的作用。连接完成之后我们还要分组进行计算,那么就需要用到下一个工具类
mycollectors是对stram中原生collectors的一个扩展,实现了更多做报表常用分组的一些操作,
mycollectorspackage collector;import utils.numberutil;import java.math.bigdecimal;import java.util.comparator;import java.util.map;import java.util.function.function;import java.util.stream.collector;import java.util.stream.collectors;public class mycollectors { /** * 返回一个collector用于对集合进行分组并且,对于组内有多个元素,只返回最后一个,其他的忽略 * 适用于明确分组key唯一的情况,value可为空 * 谨慎使用,如果分组有多条,会丢失数据!!! * @param keymapper * @param <t> * @param <k> * @param <u> * @param <m> * @return */ public static <t, k, u, m extends map<k, u>> collector<t, ?, map<k, u>> groupingbylast(function<? super t, ? extends k> keymapper, function<? super t, ? extends u> valuemapper) { return collectors.groupingby(keymapper, collectors.reducing(null, valuemapper, (o1, o2) -> o2)); } /** * 传入一个keymaper和一个比较器 * 根据key分组,组内使用比较器进行比较,最终得到一个最大结果 * @param keymapper * @param comparator * @param <t> * @param <k> * @param <u> * @param <m> * @return */ public static <t, k, u, m extends map<k, u>> collector<t, ?, map<k, t>> groupingbymaxcomparator(function<? super t, ? extends k> keymapper, comparator<t> comparator) { return collectors.groupingby(keymapper, collectors.collectingandthen(collectors.maxby(comparator), it -> it.orelse(null))); } /** * 传入一个keymaper和一个比较器 * 根据key分组,组内使用比较器进行比较,最终得到一个最小结果 * @param keymapper * @param comparator * @param <t> * @param <k> * @param <u> * @param <m> * @return */ public static <t, k, u, m extends map<k, u>> collector<t, ?, map<k, t>> groupingbymincomparator(function<? super t, ? extends k> keymapper, comparator<t> comparator) { return collectors.groupingby(keymapper, collectors.collectingandthen(collectors.maxby(comparator), it -> it.orelse(null))); } /** * 分组后组内按照指定字段求和 * @param keymapper * @param <t> * @param <k> * @return */ public static <t, k> collector<t, ?, map<k, bigdecimal>> groupingandsum(function<? super t, ? extends k> keymapper, function<? super t, bigdecimal> valuemapper) { return collectors.groupingby(keymapper, collectors.reducing(bigdecimal.zero, valuemapper, numberutil::addnumbers)); } /** * 根据对象某个字段进行求和 * @param mapper * @param <t> * @return */ public static <t> collector<t, ?, bigdecimal> sumbyfield(function<? super t, ? extends bigdecimal> mapper) { return collectors.reducing(bigdecimal.zero, mapper, numberutil::addnumbers); } /** * 求和 */ public static collector<bigdecimal, ?, bigdecimal> sum() { return collectors.reducing(bigdecimal.zero, numberutil::addnumbers); }}
组合使用的实现 map<string /* 合同变么 */, bigdecimal /* 对应moneytotal之和 */> result = collectiondatastream.of("t1", contractdetails) .joinusehashonequalcondition( contractinfos.stream().filter(it -> "60".equals(it.getstatus())).collect(collectors.tolist()), "t2", agg -> agg.gettabledata("t1").get("contractno"), contractinfo::getcontractno ).tostream("s1", contractdetail.class)//将数据流转换为 java原生stream .collect(mycollectors.groupingandsum(contractdetail::getcontractno, contractdetail::getmoneytotal));
这样的实现显然更加简单,也减少了出错的的概率,减少了代码量,提升了效率。
优势实现了集合之间的连接操作,并且是流式操作,可以一口气不断连接多个集合。
实现了与stream之间的相互转换。利用stream的功能可以实现各种复杂操作,例如过滤,转换,分组等。
效率上有一定的保证,对于等值连接采用了map优化,并且在内连接时,考虑使用后小表连大表进行优化,在一些情况下减少循环次数,在bean转换为行聚合数据时使用cglib下的beanmap减少内存的占用和性能的消耗
以上就是怎么使用java工具类实现高效编写报表的详细内容。
其它类似信息

推荐信息