<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
對於報表資料大部分情況下使用寫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 = '已簽訂’的資料
再根據 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的功能是通過介面對集合之間做關聯,實現了類似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()) )
這裡通過內連線,那麼也起到了一個過濾的作用。連線完成之後我們還要分組進行計算,那麼就需要用到下一個工具類
是對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));
這樣的實現顯然更加簡單,也減少了出錯的的概率,減少了程式碼量,提升了效率。
如果感興趣,程式碼倉庫地址為傳送門
到此這篇關於Java工具類實現高效編寫報表的文章就介紹到這了,更多相關Java編寫報表內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45