1、说明

有时候工作中需要动态生成列,也就是不确定的列,那么在数据库层就不是那么好操作了,可以使用java工具类来实现。

本工具类是对市面上的工具类进行加工改造,可以通用于各种情况,更加灵活,下面我来演示一下

2、工具类代码

package com.lili.util;


import java.lang.reflect.Field;
import java.util.*;

/**
 * 行转列终极工具类,通用于查询单个列或者多个列的结果
 *
 * @author QiJingJing
 */
public class RowConvertColUtil {
    private static Set<Object> headerSet;
    private static Set<Object> fixedColumnSet;

    private RowConvertColUtil() {
    }

    public static class ConvertData {
        private Set<Object> headerSet;
        private Set<Object> fixedColumnSet;
        private List<Map<String, Object>> dataList;

        public ConvertData(List<Map<String, Object>> dataList, Set<Object> headerSet, Set<Object> fixedColumnSet) {
            this.headerSet = headerSet;
            this.fixedColumnSet = fixedColumnSet;
            this.dataList = dataList;
        }

        public Set<Object> getHeaderSet() {
            return headerSet;
        }

        public void setHeaderSet(Set<Object> headerSet) {
            this.headerSet = headerSet;
        }

        public Set<Object> getFixedColumnSet() {
            return fixedColumnSet;
        }

        public void setFixedColumnSet(Set<Object> fixedColumnSet) {
            this.fixedColumnSet = fixedColumnSet;
        }

        public List<Map<String, Object>> getDataList() {
            return dataList;
        }

        public void setDataList(List<Map<String, Object>> dataList) {
            this.dataList = dataList;
        }
    }

    /**
     * 行转列返回 ConvertData 我们想要展示的格式
     *
     * @param orignalList     要行转列的list
     * @param headerName      要行转列的字段
     * @param fixedColumn     固定需要查询列字段
     * @param valueFiedName   行转列字段对应值列的字段名
     * @param needHeader      是否返回表头
     * @param fixedColumnName 固定需要查询列字段的昵称
     * @param nullValue       空值填充
     **/
    public static synchronized ConvertData doConvertReturnObj(List<?> orignalList, String headerName, String[] fixedColumn, String valueFiedName, boolean needHeader, String[] fixedColumnName, String nullValue) throws Exception {
        List<List<Object>> lists = doConvert(orignalList, headerName, fixedColumn, valueFiedName, needHeader, fixedColumnName, nullValue);
        // 拿到每个列表需要的属性个数
        int size = lists.get(0).size();
        // 拿出总共的集合数量
        int dataListNum = lists.size() - 1;
        // 将固定字段和固定字段值做kv映射
        Map<String, String> columnMap = new HashMap<>(16);
        for (int i = 0; i < fixedColumn.length; i++) {
            columnMap.put(fixedColumnName[i], fixedColumn[i]);
        }
        // 对lists里面的数据做转换,转换成原本类的格式(一个属性对应一个值的形式)
        List<Map<String, Object>> maps = new ArrayList<>();
        for (int i = 0; i < dataListNum; i++) {
            Map<String, Object> map = new LinkedHashMap<>(16);
            for (int j = 0; j < size; j++) {
                // 列的表头昵称
                String columnName = String.valueOf(lists.get(0).get(j));
                if (fixedColumn.length > j) {
                    // 根据昵称,拿到属性名,然后将下一个列表的对应值加进去
                    map.put(columnMap.get(columnName), lists.get(i + 1).get(j));
                } else {
                    map.put(columnName, lists.get(i + 1).get(j));
                }
            }
            maps.add(map);
        }
        return new ConvertData(maps, headerSet, fixedColumnSet);
    }

    /**
     * 列表行转列的最终结果
     *
     * @param orignalList     要行转列的list
     * @param headerName      要行转列的字段
     * @param fixedColumn     固定需要查询列字段
     * @param valueFiedName   行转列字段对应值列的字段名
     * @param needHeader      是否返回表头
     * @param fixedColumnName 固定需要查询列字段的昵称
     * @param nullValue       空值填充
     **/
    public static synchronized List<List<Object>> doConvert(List<?> orignalList, String headerName, String[] fixedColumn, String valueFiedName, boolean needHeader, String[] fixedColumnName, String nullValue) throws Exception {
        // 行转列的字段表头
        headerSet = new LinkedHashSet<>();
        // 固定列的值的集合
        fixedColumnSet = new LinkedHashSet<>();
        // 首行完整固定表头list
        List<List<Object>> resultList = new ArrayList<>();
        // 获取headerSet和fixedColumnSet
        getHeaderfixedColumnSet(orignalList, headerName, fixedColumn);
        if (needHeader) {
            List<Object> headerList = new ArrayList<>();
            //填充进header
            headerList.addAll(Arrays.asList(fixedColumnName));
            headerList.addAll(headerSet);
            resultList.add(headerList);
        }
        // 遍历固定列的值
        for (Object fixedColumnItem : fixedColumnSet) {
            // 每个固定列的值加入集合的前几个固定位置
            List<Object> colList = new ArrayList<>(Arrays.asList(fixedColumnItem.toString().split("\\|")));
            // 遍历表头
            for (Object headerItem : headerSet) {
                boolean flag = true;
                for (Object orignalObjectItem : orignalList) {
                    Field headerField = orignalObjectItem.getClass().getDeclaredField(headerName);
                    headerField.setAccessible(true);
                    // 如果表头一样
                    if (headerItem.equals(headerField.get(orignalObjectItem))) {
                        boolean flg = true;
                        Field fixedColumnField;
                        Field valueField = orignalObjectItem.getClass().getDeclaredField(valueFiedName);
                        valueField.setAccessible(true);
                        // 判断当前列是否于固定列的所有值都一样
                        for (int i = 0; i < fixedColumn.length; i++) {
                            fixedColumnField = orignalObjectItem.getClass().getDeclaredField(fixedColumn[i]);
                            fixedColumnField.setAccessible(true);
                            if (!fixedColumnItem.toString().split("\\|")[i].equals(fixedColumnField.get(orignalObjectItem).toString())) {
                                flg = false;
                            }
                        }
                        if (flg) {
                            // 如果一样的话,则将需要行转列的表头加入进来
                            colList.add(valueField.get(orignalObjectItem));
                            flag = false;
                            break;
                        }
                    }
                }
                if (flag) {
                    // 反之,加入你自定义的代替值
                    colList.add(nullValue);
                }
            }
            // 加入集合
            resultList.add(colList);
        }
        return resultList;
    }

    private static void getHeaderfixedColumnSet(List<?> orignalList, String headerName, String[] fixedColumn) {
        try {
            for (Object item : orignalList) {
                // 拿到list中每一列的行转列字段信息
                Field headerField = item.getClass().getDeclaredField(headerName);
                headerField.setAccessible(true);
                // 将值作为表头加入headerSet
                headerSet.add(headerField.get(item));
                StringBuilder sBuffer = new StringBuilder();
                int len = 1;
                for (String name : fixedColumn) {
                    Field fixedColumnField = item.getClass().getDeclaredField(name);
                    fixedColumnField.setAccessible(true);
                    // 添加每个列表固定列的值
                    sBuffer.append(fixedColumnField.get(item));
                    if (len < fixedColumn.length) {
                        // 如果有多个固定列的话,则值用|隔开
                        sBuffer.append("|");
                    }
                    len++;
                }
                // 加入固定表头值集合
                fixedColumnSet.add(sBuffer.toString());
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

3、准备工作

java 分组 行转列 java实现动态行转列_spring

Oracle查询方式如下:

 使用sql进行行转列并且查询所有字段,结果如下(我这里用的oracle)

java 分组 行转列 java实现动态行转列_java_02

Mysql查询方式如下(根据id分组)

java 分组 行转列 java实现动态行转列_java 分组 行转列_03

 用java的方式(则需要加上id,name,age这几个固定表头即可):

List<Student> list = new ArrayList<>();
list.add(new Student("1","张三",20,"语文",138.0));
list.add(new Student("2","张三",20,"数学",150.0));
list.add(new Student("3","张三",20,"英语",120.0));
list.add(new Student("4","李四",19,"语文",98.0));
list.add(new Student("5","李四",19,"数学",99.0));
list.add(new Student("6","李四",19,"英语",87.0));
list.add(new Student("7","王五",18,"语文",98.0));
list.add(new Student("8","王五",18,"数学",99.0));
list.add(new Student("9","王五",18,"英语",87.0));
list.add(new Student("10","王五",18,"物理",100.0));
String[] fixedColumn = {"id","name","age"};
String[] fixedColumnName = {"学号","姓名","年龄"};
// 要行转列的List,要行转列的字段,固定列字段数组,行转列对应值列的字段,是否返回表头,固定列字段名称数组,定义空值补数
List<List<Object>> lists = RowConvertColUtil.doConvert(list, "subject", fixedColumn, "scope", true, fixedColumnName, null);
for (List<Object> objects : lists) {
    System.out.println(objects);
}

结果如下:

java 分组 行转列 java实现动态行转列_List_04

由于我们需要把相同姓名的人放在一组,所以我们不能查询这么多字段,根据名字分组,查询名字如下 oracle方式

 

java 分组 行转列 java实现动态行转列_mybatis_05

mysql方式:

 

java 分组 行转列 java实现动态行转列_List_06

那么在我们的固定列就可只写个name即可,如下

java 分组 行转列 java实现动态行转列_spring_07

 假如有重复名字的,我们为了确保到唯一,可以加上学号标识,即固定列可以跟随自己的需求添加或者修改。演示如下:添加学号字段用来区分

java 分组 行转列 java实现动态行转列_java 分组 行转列_08

 用Oracle查询结果如下

java 分组 行转列 java实现动态行转列_java 分组 行转列_09

用mysql查询结果如下(按照名字分组,名字一样按照学号分)

java 分组 行转列 java实现动态行转列_java_10

 java工具类结果如下(再加一个即可)

java 分组 行转列 java实现动态行转列_java 分组 行转列_11

 具体固定列有多少个也就是根据什么分组,是要看本身业务需求,可以灵活变化。

这种数据格式返回给前端的话,显然还需要转换,由于是动态列的形式,这里返回的格式为List<Map<String,Object>> 格式

直接调用下面这个方法即可

return RowConvertColUtil.doConvertReturnObj(list, "subject", fixedColumn, "scope", true, fixedColumnName, null).getDataList();

页面数据显示如下:

[
    {
        "name": "张三",
        "numberNo": "111",
        "语文": 138.0,
        "数学": 150.0,
        "英语": 120.0,
        "物理": null
    },
    {
        "name": "李四",
        "numberNo": "222",
        "语文": 98.0,
        "数学": 99.0,
        "英语": 87.0,
        "物理": null
    },
    {
        "name": "王五",
        "numberNo": "333",
        "语文": 98.0,
        "数学": 99.0,
        "英语": 87.0,
        "物理": 100.0
    },
    {
        "name": "张三",
        "numberNo": "444",
        "语文": 76.0,
        "数学": 67.0,
        "英语": 98.0,
        "物理": null
    }
]