根據已有類註解作為欄位註釋,進行建表。
阿新 • • 發佈:2021-01-11
最近爬蟲專案需要根據返回的JSON結構建立相應的表,根據要求表字段必須新增儘量完善的註解。
1、通常一個JSON結構在70-250欄位之間,要根據網頁表頭與JSON資料,對比出表字段註解;
2、領域類每個欄位需要新增兩個和註解相關的註解;
3、資料來源來自多種系統,並已進行了部分取數儲存;
200多欄位對比出一堆專業名詞枯燥機械,累。
因此建立一個小工具類,從已有類中讀取其註解中的欄位註解部分,形成字典庫;然後遍歷JSON的欄位資訊進行註解新增,Model註解使用工具從表生成Java領域類,然後稍加改動即可。
主要程式碼如下:
反射工具:
1 private static final Field[] EMPTY_FIELD_ARRAY = newField[0]; 2 private static final String[] EMPTY_CAMEL_LINE_FIELD_ARRAY = new String[0]; 3 4 /** 5 * 快取 6 */ 7 private static final Map<Class<?>, Field[]> DECLARED_FIELDS_CACHE = new ConcurrentReferenceHashMap<>(256); 8 private static final Map<Class<?>, String[]> DECLARED_CAMEL_LINE_FIELDS_CACHE = newConcurrentReferenceHashMap<>(256); 9 10 /** 11 * 獲取所有欄位資訊 12 * 13 * @param classType 型別 14 * @return Field[] 15 */ 16 public static Field[] getDeclaredFields(Class<?> classType) { 17 Assert.notNull(classType, "Class must not be null"); 18Field[] result = DECLARED_FIELDS_CACHE.get(classType); 19 if (Objects.isNull(result)) { 20 try { 21 result = classType.getDeclaredFields(); 22 DECLARED_FIELDS_CACHE.put(classType, (result.length == 0 ? EMPTY_FIELD_ARRAY : result)); 23 } catch (Throwable ex) { 24 throw new IllegalStateException("Failed to introspect Class [" + classType.getName() + 25 "] from ClassLoader [" + classType.getClassLoader() + "]", ex); 26 } 27 } 28 return result; 29 } 30 31 /** 32 * 以CameLine字串形式獲取所有欄位資訊 33 * 34 * @param classType 型別 35 * @return String[] 36 */ 37 public static String[] getDeclaredCameLineFields(Class<?> classType) { 38 Assert.notNull(classType, "Class must not be null"); 39 String[] result = DECLARED_CAMEL_LINE_FIELDS_CACHE.get(classType); 40 if (Objects.isNull(result)) { 41 Field[] fields = getDeclaredFields(classType); 42 result = Arrays.stream(fields).map(field -> StringUtils.camelLine(field.getName())).toArray(String[]::new); 43 DECLARED_CAMEL_LINE_FIELDS_CACHE.put(classType, (result.length == 0 ? EMPTY_CAMEL_LINE_FIELD_ARRAY : result)); 44 } 45 return result; 46 } 47 48 /** 49 * 獲取屬性的註解值 50 * 51 * @param field 屬性 52 * @param annotationClass 註解型別 53 * @param function 轉換函式 54 * @param <T> 註解型別 55 * @param <R> 值型別 56 * @return Optional<R> 57 */ 58 public static <T extends Annotation, R> Optional<R> getDeclaredFieldAnnotation(Field field, Class<T> annotationClass, Function<T, R> function) { 59 if (field.isAnnotationPresent(annotationClass)) { 60 return Optional.ofNullable(function.apply(field.getAnnotation(annotationClass))); 61 } 62 return Optional.empty(); 63 } 64 65 /** 66 * 獲取類的欄位 -> 註解值對映 67 * 68 * @param classType 型別 69 * @param annotationClass 註解型別 70 * @param function 轉換函式 71 * @param <T> 註解型別 72 * @param <R> 值型別 73 * @return Map<Field, R> 74 */ 75 public static <T extends Annotation, R> Map<Field, R> getDeclaredFieldsAnnotation(Class<?> classType, Class<T> annotationClass, Function<T, R> function) { 76 Field[] declaredFields = getDeclaredFields(classType); 77 Map<Field, R> fieldAnnotationMap = new HashMap<>(14); 78 for (Field field : declaredFields) { 79 Optional<R> declaredFieldAnnotation = getDeclaredFieldAnnotation(field, annotationClass, function); 80 if (declaredFieldAnnotation.isPresent()) { 81 R r = declaredFieldAnnotation.get(); 82 fieldAnnotationMap.put(field, r); 83 } 84 } 85 return fieldAnnotationMap; 86 } 87 88 /** 89 * 獲取一組類的欄位 -> 註解值對映 90 * 91 * @param classTypeList 型別集合 92 * @param annotationClass 註解型別 93 * @param function 轉換函式 94 * @param <T> 註解型別 95 * @param <R> 值型別 96 * @return Map<Field, Set < R>> 97 */ 98 public static <T extends Annotation, R> Map<Field, Set<R>> getAllClassDeclaredFieldAnnotations(List<Class<?>> classTypeList, Class<T> annotationClass, Function<T, R> function) { 99 return classTypeList.stream() 100 .flatMap(classType -> getDeclaredFieldsAnnotation(classType, annotationClass, function).entrySet().stream()) 101 .collect(Collectors.toMap( 102 Map.Entry::getKey, 103 entry -> { 104 HashSet<R> rHashSet = new HashSet<>(); 105 rHashSet.add(entry.getValue()); 106 return rHashSet; 107 }, 108 (k1, k2) -> { 109 if (k1.size() > k2.size()) { 110 k1.addAll(k2); 111 return k1; 112 } else { 113 k2.addAll(k1); 114 return k2; 115 } 116 } 117 )); 118 } 119 120 /** 121 * 將Field型別鍵轉換為String型別 122 * 123 * @param fieldMap Map<String, Set<T>> 124 * @param <T> 泛型引數 125 * @return Map<String, Set < T>> 126 */ 127 public static <T> Map<String, Set<T>> fieldToCameLineOfMap(Map<Field, Set<T>> fieldMap) { 128 return fieldMap.entrySet() 129 .stream() 130 .collect(Collectors.toMap( 131 entry -> StringUtils.camelLine(entry.getKey().getName()), 132 Map.Entry::getValue, 133 // Map鍵本身不重複 134 (k1, k2) -> k1 135 )); 136 } 137 138 /** 139 * 將駝峰字串轉換為帶下劃線的字串,例如MyAccout,轉換為my_account 140 * 141 * @param str 源字串 142 * @return 轉換後的字串 143 */ 144 public static String camelLine(String str) { 145 StringBuilder stringBuilder = new StringBuilder(); 146 int index = 0; 147 for (int i = 1, length = str.length(); i < length; i++) { 148 if (Character.isUpperCase(str.charAt(i))) { 149 stringBuilder.append(str, index, i).append("_"); 150 index = i; 151 } 152 } 153 return stringBuilder.append(str.substring(index)).toString().toLowerCase(); 154 }
測試用例:
1 @Test 2 public void generateTableSQL() { 3 String modelJsonStr = ""; 4 String tableName = "table_name"; 5 String tableComment = "table_comment"; 6 7 JSONObject modelJsonObject = JSONObject.parseObject(modelJsonStr); 8 Set<Map.Entry<String, Object>> entrySet = modelJsonObject.entrySet(); 9 10 Map<Field, Set<String>> allClassDeclaredFieldAnnotations = ReflectionUtils.getAllClassDeclaredFieldAnnotations( 11 // 字典集 12 Arrays.asList( 13 // 選取**系統相關類 14 15 ), 16 ApiModelProperty.class, 17 ApiModelProperty::value); 18 Map<String, Set<String>> dict = ReflectionUtils.fieldToCameLineOfMap(allClassDeclaredFieldAnnotations); 19 Set<String> dictKeys = dict.keySet(); 20 21 StringBuilder stringBuilder = new StringBuilder(); 22 stringBuilder.append(String.format("CREATE TABLE %s (\n", tableName)) 23 .append("\tid varchar(36) NOT NULL COMMENT 'ID',\n") 24 .append("\texport_date varchar(20) NULL COMMENT '匯出時間',\n") 25 .append("\tadd_time varchar(20) NULL COMMENT '新增時間',\n") 26 .append("\tidx varchar(10) NULL COMMENT '序號',\n") 27 .append("\tdept_id varchar(50) NULL COMMENT '組織機構編碼',\n"); 28 29 Map<String, String> ignoreMap = new HashMap<>(14); 30 for (Map.Entry<String, Object> entry : entrySet) { 31 String key = entry.getKey(); 32 stringBuilder.append("\t").append(key) 33 .append(" varchar(100) NULL COMMENT '"); 34 if (dictKeys.contains(key)) { 35 // 可自定義字典查詢規則 36 stringBuilder.append(dict.get(key).stream().findFirst().orElse("").trim()); 37 } else { 38 ignoreMap.put(key, entry.getValue() == null ? "" : entry.getValue().toString()); 39 } 40 stringBuilder.append("',\n"); 41 } 42 stringBuilder.append("\tCONSTRAINT ").append(tableName).append(" PRIMARY KEY (id)\n") 43 .append(")\n") 44 .append("ENGINE=InnoDB\n") 45 .append("DEFAULT CHARSET=utf8\n") 46 .append("COLLATE=utf8_general_ci\n") 47 .append("COMMENT='").append(tableComment).append("';"); 48 // 不負責最終SQL的格式化 49 System.out.println(stringBuilder.toString()); 50 System.out.printf("字典未包含欄位:%d個\n%s\n", ignoreMap.size(), ignoreMap.entrySet().stream().map(entry -> String.format("\t\"%s\": \"%s\"", entry.getKey(), entry.getValue())).collect(Collectors.joining(",\n", "{\n", "\n}"))); 51 }