Pour les données de rapport, dans la plupart des cas, l'écriture de SQL est utilisée pour fournir des sources de données pour de grands écrans/rapports. Cependant, dans certaines situations complexes, seule l'utilisation de SQL ne peut pas être implémentée, ou lorsqu'elle l'est. difficile à mettre en œuvre, il implémentera une logique complexe via du code et renverra finalement le résultat.
Pour les rapports relativement complexes, il est souvent nécessaire de connecter des données, c'est-à-dire de joindre, regrouper et calculer des opérations entre les tables. SQL prend naturellement en charge ces opérations et est facile à mettre en œuvre. Mais lorsque nous avons besoin de connecter des données en code Java, le support natif n'est pas si convivial. Nous l'implémentons souvent comme ça
Maintenant, il y a deux collections
List<ContractDetail> contractDetails; // 合同明细集合,合同会重复 List<ContractInfo> contractInfos; // 合同主要信息,不会有重复合同
structures de données correspondantes
public class ContractDetail { /** * 合同编号 */ private String contractNo; /** * 总金额 */ private BigDecimal moneyTotal; } public class ContractInfo { /** * 合同编号 */ private String contractNo; /** * 状态 */ private String status; }
Exigences
contratDétails selon le contratPas de contrat d'associéInfos. , filtrez les données avec le statut = 'Signed'
Ensuite, regroupez-les en fonction du contratNo dans contractDetails, et trouvez la somme d'argentTotal correspondant à chaque contratNo respectivement
Le résultat final devrait être une carte
Map<String /* 合同编码 */, BigDecimal /* 对应moneyTotal之和 */> result;
Habituellement, nous ferons cette implémentation
// 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); });
Évidemment, cette implémentation est plus compliquée, car utiliser SQL n'est rien de plus que rejoindre et ajouter un groupe par. Somme. Ce problème peut être facilement résolu. Jetez ensuite un œil à la classe d’outils suivante et réfléchissez s’il existe un moyen plus simple de la mettre en œuvre.
La fonction du flux de données de collecte CollectionDataStream est d'associer des collections via des interfaces, d'implémenter deux opérations similaires à la jointure SQL et à la jointure gauche
et de réaliser la fonction de conversion mutuelle avec Stream en Java.
La structure de données d'agrégation convertit la collection en une structure de données similaire à une structure de table, y compris le nom de la table et les données
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 représente une ligne de données, la clé d'aggregationMap est le nom de la table et la valeur est celle correspondante data
Jetons un coup d'œil à cette interface en détail
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); }
Faites attention à la différence entre joinUseHashOnEqualCondition et les méthodes de jointure.
Si la connexion entre les ensembles est une connexion de valeur égale à un champ, utilisez joinUseHashOnEqualCondition, qui utilise en interne le regroupement de cartes pour rejoindre. Si vous utilisez join directement, les conditions de connexion peuvent être personnalisées, mais la condition est jugée via des doubles boucles, ce qui est moins efficace. Par conséquent, dans le cas de valeurs égales, il est plus efficace d’utiliser joinUseHashOnEqualCondition.
Ou prenez les exigences ci-dessus comme exemple
Connectez d'abord les deux collections
CollectionDataStream.of("t1", contractDetails) .joinUseHashOnEqualCondition( contractInfos.stream().filter(it -> "已签订".equals(it.getStatus())).collect(Collectors.toList()), "t2", agg -> agg.getTableData("t1").get("contractNo"), ContractInfo::getContractNo );
L'analyse du code
CollectionDataStream.of("t1", contractDetails)
consiste à convertir le contrat de collecteDétails en un flux de données avec le nom de table t1,
.joinUseHashOnEqualCondition( contractInfos.stream().filter( "t2", it -> "已签订".equals(it.getStatus())).collect(Collectors.toList()), agg -> agg.getTableData("t1").get("contractNo"), ContractInfo::getContractNo );
Connecter contractInfos, et donnez à contractInfos un alias t2. La condition de connexion est la connexion équivalente de contractNo de t1 et contractNol de contractInfos. Après la connexion, un nouveau flux de données agrégé est obtenu
Bien sûr, vous pouvez également utiliser une connexion personnalisée pour implémenter.
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()) )
Ici par connexion intérieure, Ensuite cela joue aussi un rôle de filtre. Une fois la connexion terminée, nous devons encore regrouper pour le calcul, nous devons donc utiliser la classe d'outils suivante
est une extension des Collectors natifs de stram, qui implémente des opérations de regroupement plus courantes pour les rapports,
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); } }
Mapresult = 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));
Cette implémentation est évidemment plus simple, réduit la probabilité d'erreurs, réduit la quantité de code et améliore l'efficacité.
réalise l'opération de connexion entre les collections, et il s'agit d'une opération de streaming, qui peut connecter en continu plusieurs collections en une seule fois.
réalise la conversion vers et depuis Stream. Diverses opérations complexes peuvent être mises en œuvre à l'aide des fonctions de flux, telles que le filtrage, la conversion, le regroupement, etc.
L'efficacité est garantie dans une certaine mesure. L'optimisation des cartes est utilisée pour les jointures équivalentes, et lorsque des jointures internes sont effectuées, de petites tables sont utilisées pour se connecter à de grandes tables pour l'optimisation. Dans certains cas, le nombre de boucles est réduit, et les beans sont convertis en lignes lors de l'agrégation de données, utilisez BeanMap sous cglib pour réduire l'utilisation de la mémoire et la consommation de performances
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!