大多情况下sql是可以实现数据的处理,可以减少程序去做额外的处理工作,只是有些业务情况sql做不到的,比如说本人在数据处理工作中,会涉及到对数据单独做分组展示的情况;不是sql的group by 返回list,而是返回的数据集为List<Map>等复杂的层级数组,而且具有分组的特点。
比如下面的Bean:
@Data
@Builder
@AllArgsConstructor
public class User {
private Long id;
private Long tenantId;
private String name;
private int age;
private Integer testType;
private Date testDate;
private Long role;
private String phone;
}
以 id,tenantId,age,name,phone 组合一级分组,
role 二级分组,
testType,testDate 组合三级分组,的层级展示
结果集:
[
{
"phone":"1351234567890",
"tenantId":"2",
"name":"什么",
"id":"1",
"age":"1",
"content":[
{
"role":"12",
"content":[
{
"testType":"1",
"testDate":"Sun Jun 23 13:47:46 CST 2019",
"content":[
]
}
]
}
]
},
{
"phone":"1351234567892",
"tenantId":"2",
"name":"什么",
"id":"1",
"age":"3",
"content":[
{
"role":"12",
"content":[
{
"testType":"2",
"testDate":"Sun Jun 23 13:47:46 CST 2019",
"content":[
]
}
]
}
]
},
{
"phone":"1351234567890",
"tenantId":"2",
"name":"撒子",
"id":"1",
"age":"2",
"content":[
{
"role":"12",
"content":[
{
"testType":"1",
"testDate":"Sun Jun 23 13:47:46 CST 2019",
"content":[
]
}
]
}
]
},
{
"phone":"1351234567891",
"tenantId":"2",
"name":"撒子",
"id":"1",
"age":"2",
"content":[
{
"role":"12",
"content":[
{
"testType":"1",
"testDate":"Sun Jun 23 13:47:46 CST 2019",
"content":[
]
},
{
"testType":"2",
"testDate":"Sun Jun 23 13:47:46 CST 2019",
"content":[
]
}
]
}
]
},
{
"phone":"1351234567892",
"tenantId":"2",
"name":"撒子",
"id":"1",
"age":"3",
"content":[
{
"role":"12",
"content":[
{
"testType":"2",
"testDate":"Sun Jun 23 13:47:46 CST 2019",
"content":[
]
}
]
}
]
}
]
通过数据结构Map,List进行递归算法实现,下面是源码展示和讲解:
/**
* 统一方法入口
*
* @param mocl 处理集合
* @param index 层级
* @param keyNames 字段
* @param <T>
* @return
*/
public static <T> LinkedList<Map> group(List mocl, int index, String... keyNames) {
return transform(groupClassifyList(mocl, keyNames), index);
}
index 为需要输出的层级数
keyNames 为需要分组的字段,以,号分隔.
对数据如何分组处理的呢?
public static final String SEPARATOR_FIELD = "#";
public static final String CONNECTOR_KV = "=";
public static final String SEPARATOR_REG = "\\,";
public static final String CONTENT_NULL = "null";
public static final String CONTENT_CHILDREN = "content";
/**
* 运用PropertyUtils取得bean的值,并根据keyName归类
*
* @param list List beans
* @param keyName 需要归类的bean的属性名称
* @return LinkedHashMap<String, List>,有顺序的map,map的key为需要归类的bean的属性名+"="+对应的属性值:eg:"key=value",value为List<bean>
*/
public static <T> LinkedHashMap<String, List<T>> groupClassify(List<T> list, String keyName) {
LinkedHashMap<String, List<T>> target = new LinkedHashMap();
String[] keyNames = keyName.split(SEPARATOR_REG);
for (T obj : list) {
// 取得bean需要归类的属性(keyName)的值,不做类型转换
StringBuilder keyValueBf = new StringBuilder();
for (String key : keyNames) {
Object value = null;
try {
value = PropertyUtils.getProperty(obj, key);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
keyValueBf
.append(key)
.append(CONNECTOR_KV)
.append(null == value ? "" : value)
.append(SEPARATOR_FIELD);
}
String keyValue = keyValueBf.deleteCharAt(keyValueBf.length() - 1).toString();
if (!target.containsKey(keyValue)) {
// 如果map中没有归类key值,则添加key值和相应的list
List keyList = new ArrayList();
keyList.add(obj);
target.put(keyValue, keyList);
} else {
// 如果有归类key值,则在相应的list中添加这个bean
ArrayList keyList = (ArrayList) target.get(keyValue);
keyList.add(obj);
}
}
return target;
}
将list进行keyName值进行组合成map的key=value结构,value以list形式赋值,
/**
* 将归类的Map<String, List>按照 keyName归类,并用index控制递归。
*
* @param mocl map of classified list,也就是运用方法 LinkedHashMap<String, List> groupClassify(List list, String keyName),将list归类成的map
* @param index 用条件 index < keyNames.length控制递归
* @param keyNames 需要归类的bean的属性名称
* @return
*/
private static <T> LinkedHashMap<String, Map> groupClassify(Map<String, List<T>> mocl, int index, String... keyNames) {
// 单步理解:target是函数参数Map<String, List> mocl再次归类成的LinkedHashMap<String,Map>
// 递归到最后这个是最终归类好的map
LinkedHashMap<String, Map> target = new LinkedHashMap();
// 控制递归条件,起始的index应该总是1。
if (index < keyNames.length) {
// swap用来保存参数index的值,这是最容易出错的一个地方
// 用它保证:在参数Map<String, List> mocl层面循环时用相同的index参数值。
int swap = index;
for (Map.Entry<String, List<T>> entry : mocl.entrySet()) {
String moclKey = entry.getKey();
List<T> moclList = entry.getValue();
// 将List<bean>再次归类
LinkedHashMap<String, List<T>> mocl$ = groupClassify(moclList, keyNames[index]);
// 如果index达到了数组的最后一个,一定是List<bean>转map,递归结束
if (index == keyNames.length - 1) {
target.put(moclKey, mocl$);
} else {
// 将List<bean>转map得到的_mocl,再次归类
// mocm 为map of classified map的简称
LinkedHashMap<String, Map> mocm = groupClassify(mocl$, ++index, keyNames);
target.put(moclKey, mocm);
}
index = swap;
}
}
return target;
}
/**
* 将Map<String, List> map按照bean需要归类的属性名keyName归类
*
* @param map map of classified list list归类成的map
* @param keyName bean需要归类的属性名
* @return
*/
public static <T> LinkedHashMap<String, Map> groupClassifyMap(Map<String, List<T>> map, String keyName) {
LinkedHashMap<String, Map> target = new LinkedHashMap();
for (Map.Entry<String, List<T>> entry : map.entrySet()) {
List mapList = entry.getValue();
String mapKey = entry.getKey();
LinkedHashMap<String, List<T>> keyMap = groupClassify(mapList, keyName);
target.put(mapKey, keyMap);
}
return target;
}
/**
* 将List<bean> 按照指定的bean的属性进行归类,keyNames的先后顺序会影响归类结果。
* eg:一个学生列表,按照班级和性别归类<br>
* Map map = CollectionUtils.groupClassifyList(studentList, "classId","sex");
*
* @param list List beans
* @param keyNames 数组包含需要归类的bean的属性名称
* @return 归类的有顺序的树状map,eg:"key1#key2"
* map的值为List或者map
*/
public static LinkedHashMap groupClassifyList(List list, String... keyNames) {
if (keyNames == null || keyNames.length == 0) {
return null;
}
if (keyNames.length == 1) {
return groupClassify(list, keyNames[0]);
} else {
return groupClassify(groupClassify(list, keyNames[0]), 1, keyNames);
}
}
/**
* key值转换
*
* @param value
* @return
*/
public static Map str2Map(String value) {
HashMap map = new HashMap();
String[] k_vs = value.split(SEPARATOR_FIELD);
for (int i = 0; i < k_vs.length; i++) {
String[] k_v = k_vs[i].split(CONNECTOR_KV);
if (StringUtils.isBlank(k_v[0])) {
// key为空时,是否存入
continue;
} else if (k_v.length == 1 || CONTENT_NULL.equals(k_v[1])) {
// value为空时,json格式是否显示
map.put(k_v[0], null);
} else {
map.put(k_v[0], k_v[1]);
}
}
return map;
}
/**
* 所有节点遍历转换成map
*
* @param mocl
* @param index
* @param <T>
* @return
*/
public static <T> LinkedList<Map> transform(Map<String, Object> mocl, int index) {
LinkedList<Map> root = new LinkedList<>();
for (String moclKey : mocl.keySet()) {
LinkedList<Map> objects = new LinkedList<>();
Map<Object, Object> objectMap = str2Map(moclKey);
Object obj = mocl.get(moclKey);
if (obj instanceof String) {
Map map = str2Map((String) obj);
objects.add(map);
} else if (obj instanceof List) {
LinkedList<Object> maps = transformList((List) obj, --index);
objectMap.put(CONTENT_CHILDREN, maps);
objects.add(objectMap);
} else if (obj instanceof Map) {
LinkedList<Map> maps = transformMap((Map<String, Object>) obj, --index);
objects.addAll(maps);
}
objectMap.put(CONTENT_CHILDREN, objects);
root.add(objectMap);
}
return root;
}
统一对String类型的key进行Map转换,对value进行子集转换,包括Map、List类型的content
/**
* 所有节点遍历转换成List
* 通过递归方式整理数据
*
* @param mocl
* @param index
* @param <T>
* @return
*/
public static <T> LinkedList<Object> transformList(List mocl, int index) {
// 控制递归条件,起始的index应该总是1。
LinkedList<Object> content = new LinkedList<>();
if (index <= 0) {
return content;
}
index--;
for (Object obj : mocl) {
if (obj instanceof List) {
LinkedList<Object> maps = transformList((List) obj, index);
content.addAll(maps);
} else if (obj instanceof Map) {
LinkedList<Map> maps = transformMap((Map<String, Object>) obj, index);
content.addAll(maps);
} else {
content.add(obj);
}
}
return content;
}
/**
* 所有节点遍历转换成map
* 通过递归方式整理数据
*
* @param mocl
* @param index
* @param <T>
* @return
*/
public static <T> LinkedList<Map> transformMap(Map<String, Object> mocl, int index) {
// 控制递归条件,起始的index应该总是1。
LinkedList<Map> objects = new LinkedList<>();
if (index <= 0) {
return objects;
}
// swap用来保存参数index的值,这是最容易出错的一个地方
// 用它保证:在参数Map<String, List> mocl层面循环时用相同的index参数值。
index--;
for (String moclKey : mocl.keySet()) {
Map<Object, Object> objectMap = str2Map(moclKey);
// 将List<bean>再次归类
Object object = mocl.get(moclKey);
if (object instanceof List) {
LinkedList<Object> maps = transformList((List) object, index);
objectMap.put(CONTENT_CHILDREN, maps);
objects.add(objectMap);
} else if (object instanceof Map) {
LinkedList<Map> maps = transformMap((Map<String, Object>) mocl.get(moclKey), index);
objectMap.put(CONTENT_CHILDREN, maps);
objects.add(objectMap);
}
}
return objects;
}
这时候键值对就统一做好了转换,
通过主函数一起来测试下,仅仅为测试数据
/**
* eg:
*
* @param args
*/
public static void main(String[] args) {
// 待处理信息
LinkedList<Object> objects = new LinkedList<>();
objects.add(User.builder().id(1L).tenantId(2L).age(1).name("什么").phone("1351234567890").role(12L).testDate(new Date()).testType(1).build());
objects.add(User.builder().id(1L).tenantId(2L).age(1).name("什么").phone("1351234567890").role(12L).testDate(new Date()).testType(1).build());
objects.add(User.builder().id(1L).tenantId(2L).age(2).name("撒子").phone("1351234567890").role(12L).testDate(new Date()).testType(1).build());
objects.add(User.builder().id(1L).tenantId(2L).age(2).name("撒子").phone("1351234567891").role(12L).testDate(new Date()).testType(1).build());
objects.add(User.builder().id(1L).tenantId(2L).age(2).name("撒子").phone("1351234567891").role(12L).testDate(new Date()).testType(2).build());
objects.add(User.builder().id(1L).tenantId(2L).age(3).name("撒子").phone("1351234567892").role(12L).testDate(new Date()).testType(2).build());
objects.add(User.builder().id(1L).tenantId(2L).age(3).name("什么").phone("1351234567892").role(12L).testDate(new Date()).testType(2).build());
objects.add(User.builder().id(1L).tenantId(2L).age(3).name("什么").phone("1351234567890").role(12L).testDate(new Date()).testType(2).build());
// 最终分解的信息
LinkedList<Map> role = group(objects, 1, "id,tenantId,age,name,phone", "role", "testType,testDate");
System.out.println(JsonUtils.toJson(role));
}
输出结果:
[
{
"phone":"1351234567890",
"tenantId":"2",
"name":"什么",
"id":"1",
"age":"1",
"content":[
]
},
{
"phone":"1351234567890",
"tenantId":"2",
"name":"撒子",
"id":"1",
"age":"2",
"content":[
]
},
{
"phone":"1351234567891",
"tenantId":"2",
"name":"撒子",
"id":"1",
"age":"2",
"content":[
]
},
{
"phone":"1351234567892",
"tenantId":"2",
"name":"撒子",
"id":"1",
"age":"3",
"content":[
]
},
{
"phone":"1351234567892",
"tenantId":"2",
"name":"什么",
"id":"1",
"age":"3",
"content":[
]
},
{
"phone":"1351234567890",
"tenantId":"2",
"name":"什么",
"id":"1",
"age":"3",
"content":[
]
}
]
大家可以复制代码去体验测试下输出结果。
stream 的方式也能简单实现类似的结果,
```java
Map<Integer, Map<String, List<Tuple3>>> collect1 =
Optional.ofNullable(objects)
.orElse(Collections.emptyList())
.stream()
.sorted(Comparator.comparing(User::getAge).reversed())
.collect(Collectors.groupingBy(User::getTestType,
Collectors.groupingBy(User::getName,
Collectors.collectingAndThen(Collectors.toList(),
lists -> lists.stream().map(history ->
new Tuple3(
history.getName(),
history.getPhone(),
history.getTestDate())
).collect(Collectors.toList())))));