Java工具類實(shí)現(xiàn)高效編寫報(bào)表
為什么使用java代碼寫報(bào)表
對(duì)于報(bào)表數(shù)據(jù)大部分情況下使用寫sql的方式為大屏/報(bào)表提供數(shù)據(jù)來(lái)源,但是對(duì)于某些復(fù)雜情況下僅僅使用sql無(wú)法實(shí)現(xiàn),或者實(shí)現(xiàn)起來(lái)困難的時(shí)候,會(huì)采取通過(guò)代碼實(shí)現(xiàn)復(fù)雜的邏輯最終將結(jié)果返回。
遇到的問(wèn)題
對(duì)于相對(duì)復(fù)雜的報(bào)表,經(jīng)常需要做數(shù)據(jù)的連接即表與表的join,分組,計(jì)算等操作。sql天然支持這些操作,實(shí)現(xiàn)起來(lái)很輕松。但是當(dāng)我們?cè)趈ava代碼中需要對(duì)數(shù)據(jù)進(jìn)行連接時(shí),原生支持的就并不那么友好,我們常常會(huì)這么實(shí)現(xiàn)
現(xiàn)在有兩個(gè)集合
List<ContractDetail> contractDetails; // 合同明細(xì)集合,合同會(huì)重復(fù) List<ContractInfo> contractInfos; // 合同主要信息,不會(huì)有重復(fù)合同
對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)
public class ContractDetail {
/**
* 合同編號(hào)
*/
private String contractNo;
/**
* 總金額
*/
private BigDecimal moneyTotal;
}
public class ContractInfo {
/**
* 合同編號(hào)
*/
private String contractNo;
/**
* 狀態(tài)
*/
private String status;
}需求
contractDetails 根據(jù) contractNo關(guān)聯(lián) contractInfos,過(guò)濾出status = '已簽訂’的數(shù)據(jù)
再根據(jù) contractDetails 中的contractNo分組,分別求每個(gè) contractNo對(duì)應(yīng)的moneyTotal之和
最終輸出的應(yīng)該為一個(gè)map
Map<String /* 合同編碼 */, BigDecimal /* 對(duì)應(yīng)moneyTotal之和 */> result;
通常我們會(huì)這么實(shí)現(xiàn)
// setp 1 過(guò)濾出 已簽訂狀態(tài)的合同編碼
Set<String> stopContract = contractInfos.stream()
.filter(it -> "已簽訂".equals(it.getStatus()))
.map(ContractInfo::getContractNo).collect(Collectors.toSet());
//step2 根據(jù) step1的合同編碼集合過(guò)濾出狀態(tài)正確的contractDetail
contractDetails = contractDetails.stream()
.filter(it -> stopContract.contains(it.getContractNo()))
.collect(Collectors.toList());
//step3 根據(jù)contractNo分別累加對(duì)應(yīng)的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);
});顯然這個(gè)實(shí)現(xiàn)時(shí)比較復(fù)雜的,因?yàn)槭褂胹ql的話無(wú)非就是 join 連接之后加上group by分組。求和。就可以輕易解決這個(gè)問(wèn)題。那么看看后面這個(gè)工具類,再思考有沒(méi)有更簡(jiǎn)單的辦法實(shí)現(xiàn)。
工具類
CollectionDataStream
集合數(shù)據(jù)流CollectionDataStream的功能是通過(guò)接口對(duì)集合之間做關(guān)聯(lián),實(shí)現(xiàn)了類似sql join和left join兩個(gè)操作
并且實(shí)現(xiàn)和java中的Stream相互轉(zhuǎn)換的功能。
聚合數(shù)據(jù)結(jié)構(gòu)將集合轉(zhuǎn)換成類似表結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu),包含表名,數(shù)據(jù)
public class AggregationData {
Map<String, Map> aggregationMap;
private AggregationData(){
aggregationMap = new HashMap<>();
}
//key 為別名,value為對(duì)應(yīng)對(duì)象
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代表一行數(shù)據(jù),aggregationMap的key為表名,value為對(duì)應(yīng)的數(shù)據(jù)
來(lái)詳細(xì)看看這個(gè)接口
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
public interface CollectionDataStream<T> {
/**
*將集合轉(zhuǎn)化為數(shù)據(jù)流,并給一個(gè)別名
* @param tableName
* @param collection
* @return
*/
static CollectionDataStream<AggregationData> of(String tableName, Collection<?> collection) {
return new CollectionDataStreamImpl(tableName, collection);
}
/**
*將 Stream轉(zhuǎn)化為數(shù)據(jù)流,并給一個(gè)別名
* @param tableName
* @param collection
* @return
*/
static CollectionDataStream<AggregationData> of(String tableName, Stream<?> collection) {
return new CollectionDataStreamImpl(tableName, collection);
}
/**
* 內(nèi)連接,可自定義連接條件,使用雙循環(huán)
*
* @param tableName
* @param collection
* @param predict
* @param <T1>
* @return
*/
<T1> CollectionDataStream<T> join(String tableName, Collection<T1> collection, JoinPredicate<T, T1> predict);
/**
* 等值內(nèi)連接,使用map優(yōu)化
*
* @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);
/**
* 左連接,可自定義連接條件,使用雙循環(huán)
*
* @param tableName
* @param collection
* @param predict
* @param <T1>
* @return
*/
<T1> CollectionDataStream<T> leftJoin(String tableName, Collection<T1> collection, JoinPredicate<T, T1> predict);
/**
* 等值左連接,使用map優(yōu)化
*
* @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兩個(gè)方法的區(qū)別。
如果集合之間的連接時(shí)某個(gè)字段等值連接,那么使用joinUseHashOnEqualCondition,其內(nèi)部使用的是map分組之后進(jìn)行連接。而直接使用join的話連接條件可自定義,但是是通過(guò)雙重循環(huán)進(jìn)行條件判斷,效率較低。因此等值情況下,使用joinUseHashOnEqualCondition效率更高。
如何使用
還是已上面的需求為例
先進(jìn)行兩個(gè)集合之間的連接
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轉(zhuǎn)換為表名為t1的數(shù)據(jù)流,
.joinUseHashOnEqualCondition(
contractInfos.stream().filter(
"t2",
it -> "已簽訂".equals(it.getStatus())).collect(Collectors.toList()),
agg -> agg.getTableData("t1").get("contractNo"),
ContractInfo::getContractNo
);
內(nèi)連接contractInfos,同時(shí)給contractInfos起別名t2,連接條件是等值連接 t1的contractNo和contractInfos的contractNol連接之后得到新的聚合數(shù)據(jù)流
當(dāng)然也可以使用自定義的連接實(shí)現(xiàn)
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())
)
這里通過(guò)內(nèi)連接,那么也起到了一個(gè)過(guò)濾的作用。連接完成之后我們還要分組進(jìn)行計(jì)算,那么就需要用到下一個(gè)工具類
MyCollectors
是對(duì)stram中原生Collectors的一個(gè)擴(kuò)展,實(shí)現(xiàn)了更多做報(bào)表常用分組的一些操作,
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 {
/**
* 返回一個(gè)Collector用于對(duì)集合進(jìn)行分組并且,對(duì)于組內(nèi)有多個(gè)元素,只返回最后一個(gè),其他的忽略
* 適用于明確分組key唯一的情況,value可為空
* 謹(jǐn)慎使用,如果分組有多條,會(huì)丟失數(shù)據(jù)?。?!
* @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));
}
/**
* 傳入一個(gè)keyMaper和一個(gè)比較器
* 根據(jù)key分組,組內(nèi)使用比較器進(jìn)行比較,最終得到一個(gè)最大結(jié)果
* @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)));
}
/**
* 傳入一個(gè)keyMaper和一個(gè)比較器
* 根據(jù)key分組,組內(nèi)使用比較器進(jìn)行比較,最終得到一個(gè)最小結(jié)果
* @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)));
}
/**
* 分組后組內(nèi)按照指定字段求和
* @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));
}
/**
* 根據(jù)對(duì)象某個(gè)字段進(jìn)行求和
* @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);
}
}組合使用的實(shí)現(xiàn)
Map<String /* 合同變么 */, BigDecimal /* 對(duì)應(yīng)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)//將數(shù)據(jù)流轉(zhuǎn)換為 java原生Stream
.collect(MyCollectors.groupingAndSum(ContractDetail::getContractNo, ContractDetail::getMoneyTotal));這樣的實(shí)現(xiàn)顯然更加簡(jiǎn)單,也減少了出錯(cuò)的的概率,減少了代碼量,提升了效率。
優(yōu)勢(shì)
- 實(shí)現(xiàn)了集合之間的連接操作,并且是流式操作,可以一口氣不斷連接多個(gè)集合。
- 實(shí)現(xiàn)了與Stream之間的相互轉(zhuǎn)換。利用stream的功能可以實(shí)現(xiàn)各種復(fù)雜操作,例如過(guò)濾,轉(zhuǎn)換,分組等。
- 效率上有一定的保證,對(duì)于等值連接采用了Map優(yōu)化,并且在內(nèi)連接時(shí),考慮使用后小表連大表進(jìn)行優(yōu)化,在一些情況下減少循環(huán)次數(shù),在bean轉(zhuǎn)換為行聚合數(shù)據(jù)時(shí)使用cglib下的BeanMap減少內(nèi)存的占用和性能的消耗
如果感興趣,代碼倉(cāng)庫(kù)地址為傳送門
到此這篇關(guān)于Java工具類實(shí)現(xiàn)高效編寫報(bào)表的文章就介紹到這了,更多相關(guān)Java編寫報(bào)表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)Fibonacci(斐波那契)取余的示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)Fibonacci取余的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Mybatis和orcale update語(yǔ)句中接收參數(shù)為對(duì)象的實(shí)例代碼
Mybatis的 mapper.xml 中 update 語(yǔ)句使用 if 標(biāo)簽判斷對(duì)像屬性是否為空值。本文重點(diǎn)給大家介紹Mybatis和orcale update語(yǔ)句中接收參數(shù)為對(duì)象的實(shí)例代碼,需要的朋友參考下吧2017-09-09
java 中HashMap實(shí)現(xiàn)原理深入理解
這篇文章主要介紹了java 中HashMap實(shí)現(xiàn)原理深入理解的相關(guān)資料,需要的朋友可以參考下2017-03-03
Java如何向主函數(shù)main中傳入?yún)?shù)
這篇文章主要介紹了Java如何向主函數(shù)main中傳入?yún)?shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
SpringBoot實(shí)現(xiàn)WebSocket即時(shí)通訊的示例代碼
本文主要介紹了SpringBoot實(shí)現(xiàn)WebSocket即時(shí)通訊的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
詳解基于MybatisPlus兩步實(shí)現(xiàn)多租戶方案
這篇文章主要介紹了詳解基于MybatisPlus兩步實(shí)現(xiàn)多租戶方案,本文分兩步,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
SpringBoot集成Lettuce客戶端操作Redis的實(shí)現(xiàn)
本文主要介紹了SpringBoot集成Lettuce客戶端操作Redis的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
Socket與ServerSocket類構(gòu)造方法與API
今天小編為大家整理了Socket與ServerSocket類構(gòu)造方法與API,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值。需要的朋友可以收藏下,方便下次瀏覽觀看2021-12-12

