1. 程式人生 > 實用技巧 >根據已有類註解作為欄位註釋,進行建表。

根據已有類註解作為欄位註釋,進行建表。

最近爬蟲專案需要根據返回的JSON結構建立相應的表,根據要求表字段必須新增儘量完善的註解。

1、通常一個JSON結構在70-250欄位之間,要根據網頁表頭與JSON資料,對比出表字段註解;

2、領域類每個欄位需要新增兩個和註解相關的註解;

3、資料來源來自多種系統,並已進行了部分取數儲存;

200多欄位對比出一堆專業名詞枯燥機械,累。

因此建立一個小工具類,從已有類中讀取其註解中的欄位註解部分,形成字典庫;然後遍歷JSON的欄位資訊進行註解新增,Model註解使用工具從表生成Java領域類,然後稍加改動即可。

主要程式碼如下:

反射工具:

  1 private static final Field[] EMPTY_FIELD_ARRAY = new
Field[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 = new
ConcurrentReferenceHashMap<>(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"); 18
Field[] 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     }