根据已有类注解作为字段注释,进行建表。
最近爬虫项目需要根据返回的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 }