ホームページ  >  記事  >  Java  >  Java ツール クラスを使用してレポートを効率的に作成する方法

Java ツール クラスを使用してレポートを効率的に作成する方法

WBOY
WBOY転載
2023-04-14 19:16:101128ブラウズ

レポートの作成に Java コードを使用する理由

レポート データの場合、ほとんどの場合、大きな画面やレポートにデータ ソースを提供するために SQL の作成が使用されますが、複雑な場合には SQL を使用して実現できない場合があります。単独で実装するか、実装が難しい場合は、複雑なロジックをコードで実装し、最終的に結果を返します。

発生した問題

比較的複雑なレポートの場合、多くの場合、データ接続、つまりテーブル間の結合、グループ化、計算などの操作を実行する必要があります。 SQL はこれらの操作を自然にサポートしており、実装は簡単です。しかし、Java コードでデータを接続する必要がある場合、ネイティブ サポートはそれほどフレンドリーではありません。多くの場合、次のように実装します。

現在、2 つのコレクションがあります

List<ContractDetail> contractDetails; // 合同明细集合,合同会重复
List<ContractInfo> contractInfos; // 合同主要信息,不会有重复合同

対応するデータ構造

public class ContractDetail {
    /**
     * 合同编号
     */
    private String contractNo;
    /**
     * 总金额
     */
    private BigDecimal moneyTotal;
}
public class ContractInfo {
    /**
     * 合同编号
     */
    private String contractNo;
    /**
     * 状态
     */
    private String status;
}

Requirements

contractDetailscontractNo に従ってcontractInfo を関連付け、status = 'Signed' でデータをフィルタリングします。

次に、contractDetails の ContractNo に従ってグループ化し、各契約 No の対応関係をそれぞれ見つけます。 sum ofmoneyTotal

最終出力はマップになるはずです

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 の後にグループごとにグループ化が続きます。和。この問題は簡単に解決できます。次に、次のツール クラスを見て、それを実装するより簡単な方法があるかどうかを考えてください。

ツール クラス

CollectionDataStream

コレクション データ ストリーム CollectionDataStream の機能は、インターフェイスを通じてコレクションを関連付け、SQL 結合と左結合に似た 2 つの操作を実装することです

そしてStreamとの間の変換機能をJavaで実装します。

集計データ構造は、コレクションをテーブル名とデータを含むテーブル構造に似たデータ構造に変換します

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 のキーはテーブルです名前、値は対応するデータです

このインターフェイスを詳しく見てみましょう

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 を使用します。 joinを直接使用すると接続条件をカスタマイズできますが、二重ループで条件を判定するため効率が悪くなります。したがって、値が等しい場合は、joinUseHashOnEqualCondition を使用する方が効率的です。

使用方法

または、上記の要件を例として取り上げます

最初に 2 つのコレクションを接続します

 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 のネイティブ コレクターの拡張機能であり、より一般的に実装されています。一部の操作、

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 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));

この種の実装は明らかに単純で、エラーの可能性が減り、コード量が減り、効率が向上します。

メリット

  • コレクション間の接続操作を実現しており、複数のコレクションを一度に連続接続できるストリーミング操作です。

  • ストリームとの間の変換を実装します。フィルタリング、変換、グループ化など、ストリームの機能を使用してさまざまな複雑な操作を実装できます。

  • 効率性には一定の保証があります。マップの最適化は等価結合に使用され、内部結合が行われる場合は、最適化のために小さなテーブルが大きなテーブルに接続されていると見なされます。サイクル数、Bean を行集合データに変換する際のメモリ使用量とパフォーマンス消費を削減するには、cglib で BeanMap を使用します。

以上がJava ツール クラスを使用してレポートを効率的に作成する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。