Grouping a class using stream java 8 or in SQL whichever is best

一个人想着一个人 提交于 2020-01-16 09:01:31

问题


The data is something like this

    head child assigned total
[   Mas   Mas1     2    5
,   Mas   Mas2     0    5
,   Usr   usr1     4    4
,   Usr   usr2     1    3
,   Inv   Inv1     3    5
,   Inv   Inv2     2    3
,   Inv   Inv3     2    3
,   Inv   Inv4     1    3
]

I want sum of their child for a particular header.

I am using partition in sql

select head,
SUM(childAssigned) over (partition by am.acl_group) as assignedHead,
sum(childTotal) over (partition by am.acl_group) as totalHead,
child,
childAssigned,
childTotal

Since, "partition by" is not supported by HQL astquerytranslatorfactory, I am using this as a native query

I have a class which is holding SQL Response

class AclList{
    String head;
    Integer assignedHead;
    Integer totalHead;
    String child;
    Integer assignedChild;
    Integer totalChild;
}

The data is something like this

[   Mas 2   10  Mas1    2   5
,   Mas 2   10  Mas2    0   5
,   Usr 5   7   usr1    4   4
,   Usr 5   7   usr2    1   3
,   Inv 8   14  Inv1    3   5
,   Inv 8   14  Inv2    2   3
,   Inv 8   14  Inv3    2   3
,   Inv 8   14  Inv4    1   3
]

I want a response which will groupify child of same header.

Response should be like ResponseClass(type, assigned, total, List)

[ 
  { 
    "type":"MAS",
    "assigned":"2",
    "total":"10",
    "subType":[ 
      { 
        "type":"MAS1",
        "assigned":"2",
        "total":"5"
      },
      { 
        "type":"MAS2",
        "assigned":"0",
        "total":"5"
      }
    ]
  },
  { 
    "type":"USR",
    "assigned":"5",
    "total":"7",
    "subType":[ 
      { 
        "type":"USR1",
        "assigned":"4",
        "total":"4"
      },
      { 
        "type":"USR2",
        "assigned":"1",
        "total":"3"
      }
    ]
  }
]

class Details(type, assigned, total) My approach is keeping header in hashmap's key, child in hashmap's value Since all 3 columns of a head are same, So, equals, hashcode take care.

class ChildDetails(List) HashMap

Iterate sql response
if hashmap.contains(head) //exist
    fetch value, add new one in list
    hashmap.put(head, updated)
else
    create header,
    create child, add it to a blank list
    hashmap.put(head, new list)

Again iterate hashmap, arrange it in new json response

But, this is a cumbersome proces & inefficient.

Is there anyway it can be done with JAVA stream() ?


回答1:


Yes it can be done with JAVA stream()

package com.bbc.enums;

import lombok.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.*;
import java.util.stream.Collectors;


public class StackOverFlow {
    public static void main(String[] args) {
        List<AclList> aclLists = Arrays.asList(
                new AclList("Mas", 2, 10, "Mas1", 2, 5),
                new AclList("Mas", 2, 10, "Mas2", 0, 5),
                new AclList("USR", 5, 7, "USR1", 4, 4),
                new AclList("USR", 5, 7, "USR2", 1, 3),
                new AclList("Inv", 8, 14, "Inv1", 3, 5),
                new AclList("Inv", 8, 14, "Inv2", 2, 3),
                new AclList("Inv", 8, 14, "Inv3", 2, 3),
                new AclList("Inv", 8, 14, "Inv4", 1, 3)

        );

        Map<String, Map<Integer, Map<Integer, List<AclList>>>> collect = aclLists.stream()
                .collect(Collectors.groupingBy(AclList::getHead, Collectors.groupingBy(AclList::getAssignedHead,
                        Collectors.groupingBy(AclList::getTotalHead))));

        List<HashMap<String, Object>> resList = new ArrayList<>();
        collect.forEach((k1, v1) -> {
            HashMap<String, Object> res = new HashMap<>();
            res.put("type", k1);
            v1.forEach((k2, v2) -> {
                res.put("assigned", k2);
                v2.forEach((k3, v3) -> {
                    res.put("total", k3);
                    List<HashMap<String, Object>> list = new ArrayList<>();
                    v3.forEach(aclList -> {
                        HashMap<String, Object> map1 = new HashMap<>();
                        map1.put("type", aclList.getChild());
                        map1.put("assigned", aclList.getAssignedChild());
                        map1.put("total", aclList.getTotalChild());
                        list.add(map1);
                    });
                    res.put("subType", list);
                });
            });
            resList.add(res);
        });
        System.out.println(resList);
        // if you want json response then
        System.out.println(listmap_to_json_string(resList));
    }


    public static String listmap_to_json_string(List<HashMap<String, Object>> list) {
        JSONArray json_arr = new JSONArray();
        for (Map<String, Object> map : list) {
            JSONObject json_obj = new JSONObject();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                try {
                    json_obj.put(key, value);
                } catch (JSONException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            json_arr.put(json_obj);
        }
        return json_arr.toString();
    }
}


@AllArgsConstructor
@Getter
@Setter
@ToString
@NoArgsConstructor
class AclList {
    String head;
    Integer assignedHead;
    Integer totalHead;
    String child;
    Integer assignedChild;
    Integer totalChild;
}



回答2:


The Collectors.groupingBy stream Collector can help you here.

Setup:

// original data AclList
class AclList {

  String head;
  Integer assignedHead;
  Integer totalHead;
  String child;
  Integer assignedChild;
  Integer totalChild;
}

// Subtype in the response object based on Json
class SubType {

  String type;
  Integer assigned;
  Integer total;

  public SubType(String type, Integer assigned, Integer total) {
    this.type = type;
    this.assigned = assigned;
    this.total = total;
  }
}

// Response object based on Json
class Response {

  String type;
  Integer assigned;
  Integer total;
  List<SubType> subType;

  public Response(String type, Integer assigned, Integer total, List<SubType> subType) {
    this.type = type;
    this.assigned = assigned;
    this.total = total;
    this.subType = subType;
  }
}

Step 1: Group the data by the head field in the class AclList. This step returns a Map of the distinct head values as the key, and a list of AclList objects as the value, grouped by the key.

Map<String, List<AclList>> groupedByHead = data
    .stream()
    .collect(Collectors.groupingBy(acl -> acl.head));

Step 2: Just manipulate the Map to create the required response structure.

List<Response> response = groupedByHead.entrySet()
    .stream()
    .filter(e -> e.getValue().size() > 0)
    .map(e -> {
          List<AclList> acls = e.getValue();
          AclList first = acls.get(0);
          List<SubType> children = acls
              .stream()
              .map(acl -> new SubType(acl.child, acl.assignedChild, acl.totalChild))
              .collect(Collectors.toList());

          return new Response(first.head, first.assignedHead, first.totalHead, children);
    })
    .collect(Collectors.toList());

Note: I'm not sure about your constraints, but it would be better do just do the grouping operations in your database queries instead of doing it in code.



来源:https://stackoverflow.com/questions/58674170/grouping-a-class-using-stream-java-8-or-in-sql-whichever-is-best

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!