1、说明
有时候工作中需要动态生成列,也就是不确定的列,那么在数据库层就不是那么好操作了,可以使用java工具类来实现。
本工具类是对市面上的工具类进行加工改造,可以通用于各种情况,更加灵活,下面我来演示一下
2、工具类代码
1. package com.lili.util; 2. 3. 4. import java.lang.reflect.Field; 5. import java.util.*; 6. 7. /** 8. * 行转列终极工具类,通用于查询单个列或者多个列的结果 9. * 10. * @author QiJingJing 11. */ 12. public class RowConvertColUtil { 13. private static Set<Object> headerSet; 14. private static Set<Object> fixedColumnSet; 15. 16. private RowConvertColUtil() { 17. } 18. 19. public static class ConvertData { 20. private Set<Object> headerSet; 21. private Set<Object> fixedColumnSet; 22. private List<Map<String, Object>> dataList; 23. 24. public ConvertData(List<Map<String, Object>> dataList, Set<Object> headerSet, Set<Object> fixedColumnSet) { 25. this.headerSet = headerSet; 26. this.fixedColumnSet = fixedColumnSet; 27. this.dataList = dataList; 28. } 29. 30. public Set<Object> getHeaderSet() { 31. return headerSet; 32. } 33. 34. public void setHeaderSet(Set<Object> headerSet) { 35. this.headerSet = headerSet; 36. } 37. 38. public Set<Object> getFixedColumnSet() { 39. return fixedColumnSet; 40. } 41. 42. public void setFixedColumnSet(Set<Object> fixedColumnSet) { 43. this.fixedColumnSet = fixedColumnSet; 44. } 45. 46. public List<Map<String, Object>> getDataList() { 47. return dataList; 48. } 49. 50. public void setDataList(List<Map<String, Object>> dataList) { 51. this.dataList = dataList; 52. } 53. } 54. 55. /** 56. * 行转列返回 ConvertData 我们想要展示的格式 57. * 58. * @param orignalList 要行转列的list 59. * @param headerName 要行转列的字段 60. * @param fixedColumn 固定需要查询列字段 61. * @param valueFiedName 行转列字段对应值列的字段名 62. * @param needHeader 是否返回表头 63. * @param fixedColumnName 固定需要查询列字段的昵称 64. * @param nullValue 空值填充 65. **/ 66. public static synchronized ConvertData doConvertReturnObj(List<?> orignalList, String headerName, String[] fixedColumn, String valueFiedName, boolean needHeader, String[] fixedColumnName, String nullValue) throws Exception { 67. List<List<Object>> lists = doConvert(orignalList, headerName, fixedColumn, valueFiedName, needHeader, fixedColumnName, nullValue); 68. // 拿到每个列表需要的属性个数 69. int size = lists.get(0).size(); 70. // 拿出总共的集合数量 71. int dataListNum = lists.size() - 1; 72. // 将固定字段和固定字段值做kv映射 73. Map<String, String> columnMap = new HashMap<>(16); 74. for (int i = 0; i < fixedColumn.length; i++) { 75. columnMap.put(fixedColumnName[i], fixedColumn[i]); 76. } 77. // 对lists里面的数据做转换,转换成原本类的格式(一个属性对应一个值的形式) 78. List<Map<String, Object>> maps = new ArrayList<>(); 79. for (int i = 0; i < dataListNum; i++) { 80. Map<String, Object> map = new LinkedHashMap<>(16); 81. for (int j = 0; j < size; j++) { 82. // 列的表头昵称 83. String columnName = String.valueOf(lists.get(0).get(j)); 84. if (fixedColumn.length > j) { 85. // 根据昵称,拿到属性名,然后将下一个列表的对应值加进去 86. map.put(columnMap.get(columnName), lists.get(i + 1).get(j)); 87. } else { 88. map.put(columnName, lists.get(i + 1).get(j)); 89. } 90. } 91. maps.add(map); 92. } 93. return new ConvertData(maps, headerSet, fixedColumnSet); 94. } 95. 96. /** 97. * 列表行转列的最终结果 98. * 99. * @param orignalList 要行转列的list 100. * @param headerName 要行转列的字段 101. * @param fixedColumn 固定需要查询列字段 102. * @param valueFiedName 行转列字段对应值列的字段名 103. * @param needHeader 是否返回表头 104. * @param fixedColumnName 固定需要查询列字段的昵称 105. * @param nullValue 空值填充 106. **/ 107. public static synchronized List<List<Object>> doConvert(List<?> orignalList, String headerName, String[] fixedColumn, String valueFiedName, boolean needHeader, String[] fixedColumnName, String nullValue) throws Exception { 108. // 行转列的字段表头 109. headerSet = new LinkedHashSet<>(); 110. // 固定列的值的集合 111. fixedColumnSet = new LinkedHashSet<>(); 112. // 首行完整固定表头list 113. List<List<Object>> resultList = new ArrayList<>(); 114. // 获取headerSet和fixedColumnSet 115. getHeaderfixedColumnSet(orignalList, headerName, fixedColumn); 116. if (needHeader) { 117. List<Object> headerList = new ArrayList<>(); 118. //填充进header 119. headerList.addAll(Arrays.asList(fixedColumnName)); 120. headerList.addAll(headerSet); 121. resultList.add(headerList); 122. } 123. // 遍历固定列的值 124. for (Object fixedColumnItem : fixedColumnSet) { 125. // 每个固定列的值加入集合的前几个固定位置 126. List<Object> colList = new ArrayList<>(Arrays.asList(fixedColumnItem.toString().split("\\|"))); 127. // 遍历表头 128. for (Object headerItem : headerSet) { 129. boolean flag = true; 130. for (Object orignalObjectItem : orignalList) { 131. Field headerField = orignalObjectItem.getClass().getDeclaredField(headerName); 132. headerField.setAccessible(true); 133. // 如果表头一样 134. if (headerItem.equals(headerField.get(orignalObjectItem))) { 135. boolean flg = true; 136. Field fixedColumnField; 137. Field valueField = orignalObjectItem.getClass().getDeclaredField(valueFiedName); 138. valueField.setAccessible(true); 139. // 判断当前列是否于固定列的所有值都一样 140. for (int i = 0; i < fixedColumn.length; i++) { 141. fixedColumnField = orignalObjectItem.getClass().getDeclaredField(fixedColumn[i]); 142. fixedColumnField.setAccessible(true); 143. if (!fixedColumnItem.toString().split("\\|")[i].equals(fixedColumnField.get(orignalObjectItem).toString())) { 144. flg = false; 145. } 146. } 147. if (flg) { 148. // 如果一样的话,则将需要行转列的表头加入进来 149. colList.add(valueField.get(orignalObjectItem)); 150. flag = false; 151. break; 152. } 153. } 154. } 155. if (flag) { 156. // 反之,加入你自定义的代替值 157. colList.add(nullValue); 158. } 159. } 160. // 加入集合 161. resultList.add(colList); 162. } 163. return resultList; 164. } 165. 166. private static void getHeaderfixedColumnSet(List<?> orignalList, String headerName, String[] fixedColumn) { 167. try { 168. for (Object item : orignalList) { 169. // 拿到list中每一列的行转列字段信息 170. Field headerField = item.getClass().getDeclaredField(headerName); 171. headerField.setAccessible(true); 172. // 将值作为表头加入headerSet 173. headerSet.add(headerField.get(item)); 174. StringBuilder sBuffer = new StringBuilder(); 175. int len = 1; 176. for (String name : fixedColumn) { 177. Field fixedColumnField = item.getClass().getDeclaredField(name); 178. fixedColumnField.setAccessible(true); 179. // 添加每个列表固定列的值 180. sBuffer.append(fixedColumnField.get(item)); 181. if (len < fixedColumn.length) { 182. // 如果有多个固定列的话,则值用|隔开 183. sBuffer.append("|"); 184. } 185. len++; 186. } 187. // 加入固定表头值集合 188. fixedColumnSet.add(sBuffer.toString()); 189. } 190. } catch (NoSuchFieldException | IllegalAccessException e) { 191. e.printStackTrace(); 192. } 193. } 194. }
3、准备工作
Oracle查询方式如下:
使用sql进行行转列并且查询所有字段,结果如下(我这里用的oracle)
Mysql查询方式如下(根据id分组):
用java的方式(则需要加上id,name,age这几个固定表头即可):
1. List<Student> list = new ArrayList<>(); 2. list.add(new Student("1","张三",20,"语文",138.0)); 3. list.add(new Student("2","张三",20,"数学",150.0)); 4. list.add(new Student("3","张三",20,"英语",120.0)); 5. list.add(new Student("4","李四",19,"语文",98.0)); 6. list.add(new Student("5","李四",19,"数学",99.0)); 7. list.add(new Student("6","李四",19,"英语",87.0)); 8. list.add(new Student("7","王五",18,"语文",98.0)); 9. list.add(new Student("8","王五",18,"数学",99.0)); 10. list.add(new Student("9","王五",18,"英语",87.0)); 11. list.add(new Student("10","王五",18,"物理",100.0)); 12. String[] fixedColumn = {"id","name","age"}; 13. String[] fixedColumnName = {"学号","姓名","年龄"}; 14. // 要行转列的List,要行转列的字段,固定列字段数组,行转列对应值列的字段,是否返回表头,固定列字段名称数组,定义空值补数 15. List<List<Object>> lists = RowConvertColUtil.doConvert(list, "subject", fixedColumn, "scope", true, fixedColumnName, null); 16. for (List<Object> objects : lists) { 17. System.out.println(objects); 18. }
结果如下:
由于我们需要把相同姓名的人放在一组,所以我们不能查询这么多字段,根据名字分组,查询名字如下 oracle方式
mysql方式:
那么在我们的固定列就可只写个name即可,如下
假如有重复名字的,我们为了确保到唯一,可以加上学号标识,即固定列可以跟随自己的需求添加或者修改。演示如下:添加学号字段用来区分
用Oracle查询结果如下
用mysql查询结果如下(按照名字分组,名字一样按照学号分)
java工具类结果如下(再加一个即可)
具体固定列有多少个也就是根据什么分组,是要看本身业务需求,可以灵活变化。
这种数据格式返回给前端的话,显然还需要转换,由于是动态列的形式,这里返回的格式为List<Map<String,Object>> 格式
直接调用下面这个方法即可
return RowConvertColUtil.doConvertReturnObj(list, "subject", fixedColumn, "scope", true, fixedColumnName, null).getDataList();
页面数据显示如下:
1. [ 2. { 3. "name": "张三", 4. "numberNo": "111", 5. "语文": 138.0, 6. "数学": 150.0, 7. "英语": 120.0, 8. "物理": null 9. }, 10. { 11. "name": "李四", 12. "numberNo": "222", 13. "语文": 98.0, 14. "数学": 99.0, 15. "英语": 87.0, 16. "物理": null 17. }, 18. { 19. "name": "王五", 20. "numberNo": "333", 21. "语文": 98.0, 22. "数学": 99.0, 23. "英语": 87.0, 24. "物理": 100.0 25. }, 26. { 27. "name": "张三", 28. "numberNo": "444", 29. "语文": 76.0, 30. "数学": 67.0, 31. "英语": 98.0, 32. "物理": null 33. } 34. ]