001/* 002 * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.core.table; 017 018import com.mybatisflex.annotation.*; 019import com.mybatisflex.core.BaseMapper; 020import com.mybatisflex.core.FlexGlobalConfig; 021import com.mybatisflex.core.exception.FlexExceptions; 022import com.mybatisflex.core.query.QueryChain; 023import com.mybatisflex.core.query.QueryColumn; 024import com.mybatisflex.core.query.QueryCondition; 025import com.mybatisflex.core.query.QueryWrapper; 026import com.mybatisflex.core.util.ClassUtil; 027import com.mybatisflex.core.util.CollectionUtil; 028import com.mybatisflex.core.util.Reflectors; 029import com.mybatisflex.core.util.StringUtil; 030import org.apache.ibatis.io.ResolverUtil; 031import org.apache.ibatis.reflection.Reflector; 032import org.apache.ibatis.reflection.TypeParameterResolver; 033import org.apache.ibatis.session.Configuration; 034import org.apache.ibatis.type.*; 035import org.apache.ibatis.util.MapUtil; 036 037import java.lang.reflect.*; 038import java.math.BigDecimal; 039import java.math.BigInteger; 040import java.sql.Time; 041import java.sql.Timestamp; 042import java.time.*; 043import java.time.chrono.JapaneseDate; 044import java.util.*; 045import java.util.concurrent.ConcurrentHashMap; 046import java.util.stream.Collectors; 047 048public class TableInfoFactory { 049 050 private TableInfoFactory() { 051 } 052 053 public static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet( 054 int.class, Integer.class, 055 short.class, Short.class, 056 long.class, Long.class, 057 float.class, Float.class, 058 double.class, Double.class, 059 boolean.class, Boolean.class, 060 Date.class, java.sql.Date.class, Time.class, Timestamp.class, 061 Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class, 062 Year.class, Month.class, YearMonth.class, JapaneseDate.class, 063 byte[].class, Byte[].class, Byte.class, 064 BigInteger.class, BigDecimal.class, 065 char.class, String.class, Character.class 066 ); 067 068 static final Set<Class<?>> ignoreColumnTypes = CollectionUtil.newHashSet( 069 QueryWrapper.class, QueryColumn.class, QueryCondition.class, QueryChain.class 070 ); 071 072 073 private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>(); 074 private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>(); 075 private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>(); 076 private static final Set<String> initedPackageNames = new HashSet<>(); 077 078 079 public synchronized static void init(String mapperPackageName) { 080 if (!initedPackageNames.contains(mapperPackageName)) { 081 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); 082 resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName); 083 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 084 for (Class<? extends Class<?>> mapperClass : mapperSet) { 085 ofMapperClass(mapperClass); 086 } 087 initedPackageNames.add(mapperPackageName); 088 } 089 } 090 091 092 public static TableInfo ofMapperClass(Class<?> mapperClass) { 093 return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> { 094 Class<?> entityClass = getEntityClass(mapperClass); 095 if (entityClass == null) { 096 return null; 097 } 098 return ofEntityClass(entityClass); 099 }); 100 } 101 102 103 public static TableInfo ofEntityClass(Class<?> entityClass) { 104 return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> { 105 TableInfo tableInfo = createTableInfo(entityClass); 106 tableInfoMap.put(tableInfo.getTableNameWithSchema(), tableInfo); 107 return tableInfo; 108 }); 109 } 110 111 112 public static TableInfo ofTableName(String tableName) { 113 return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null; 114 } 115 116 117 private static Class<?> getEntityClass(Class<?> mapperClass) { 118 if (mapperClass == null || mapperClass == Object.class) { 119 return null; 120 } 121 return getEntityClass(mapperClass, null); 122 } 123 124 private static Class<?> getEntityClass(Class<?> mapperClass, Type[] actualTypeArguments) { 125 // 检查基接口 126 Type[] genericInterfaces = mapperClass.getGenericInterfaces(); 127 for (Type type : genericInterfaces) { 128 if (type instanceof ParameterizedType) { 129 // 泛型基接口 130 ParameterizedType parameterizedType = (ParameterizedType) type; 131 Type rawType = parameterizedType.getRawType(); 132 Type[] typeArguments = parameterizedType.getActualTypeArguments(); 133 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 134 if (rawType == BaseMapper.class) { 135 // 找到了 136 if (typeArguments[0] instanceof Class) { 137 return (Class<?>) typeArguments[0]; 138 } 139 } else if (rawType instanceof Class) { 140 // 其他泛型基接口 141 Class<?> entityClass = getEntityClass((Class<?>) rawType, typeArguments); 142 if (entityClass != null) { 143 return entityClass; 144 } 145 } 146 } else if (type instanceof Class) { 147 // 其他基接口 148 Class<?> entityClass = getEntityClass((Class<?>) type); 149 if (entityClass != null) { 150 return entityClass; 151 } 152 } 153 } 154 // 检查基类 155 Class<?> superclass = mapperClass.getSuperclass(); 156 if (superclass == null || superclass == Object.class) { 157 return null; 158 } 159 Type[] typeArguments = superclass.getTypeParameters(); 160 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 161 return getEntityClass(superclass, typeArguments); 162 } 163 164 private static void adjustTypeArguments(Class<?> subclass, Type[] subclassTypeArguments, Type[] typeArguments) { 165 for (int i = 0; i < typeArguments.length; i++) { 166 if (typeArguments[i] instanceof TypeVariable) { 167 TypeVariable<?> typeVariable = (TypeVariable<?>) typeArguments[i]; 168 TypeVariable<?>[] typeParameters = subclass.getTypeParameters(); 169 for (int j = 0; j < typeParameters.length; j++) { 170 if (Objects.equals(typeVariable.getName(), typeParameters[j].getName())) { 171 typeArguments[i] = subclassTypeArguments[j]; 172 break; 173 } 174 } 175 } 176 } 177 } 178 179 180 private static TableInfo createTableInfo(Class<?> entityClass) { 181 182 TableInfo tableInfo = new TableInfo(); 183 tableInfo.setEntityClass(entityClass); 184 Reflector reflector = Reflectors.of(entityClass); 185 tableInfo.setReflector(reflector); 186 187 //初始化表名 188 Table table = entityClass.getAnnotation(Table.class); 189 if (table != null) { 190 tableInfo.setSchema(table.schema()); 191 tableInfo.setTableName(table.value()); 192 tableInfo.setCamelToUnderline(table.camelToUnderline()); 193 194 if (table.onInsert().length > 0) { 195 List<InsertListener> insertListeners = Arrays.stream(table.onInsert()) 196 .filter(listener -> listener != NoneListener.class) 197 .map(ClassUtil::newInstance) 198 .collect(Collectors.toList()); 199 tableInfo.setOnInsertListeners(insertListeners); 200 } 201 202 if (table.onUpdate().length > 0) { 203 List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate()) 204 .filter(listener -> listener != NoneListener.class) 205 .map(ClassUtil::newInstance) 206 .collect(Collectors.toList()); 207 tableInfo.setOnUpdateListeners(updateListeners); 208 } 209 210 if (table.onSet().length > 0) { 211 List<SetListener> setListeners = Arrays.stream(table.onSet()) 212 .filter(listener -> listener != NoneListener.class) 213 .map(ClassUtil::newInstance) 214 .collect(Collectors.toList()); 215 tableInfo.setOnSetListeners(setListeners); 216 } 217 218 if (StringUtil.isNotBlank(table.dataSource())) { 219 tableInfo.setDataSource(table.dataSource()); 220 } 221 } else { 222 //默认为类名转驼峰下划线 223 String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName()); 224 tableInfo.setTableName(tableName); 225 } 226 227 //初始化字段相关 228 List<ColumnInfo> columnInfoList = new ArrayList<>(); 229 List<IdInfo> idInfos = new ArrayList<>(); 230 231 232 String logicDeleteColumn = null; 233 String versionColumn = null; 234 String tenantIdColumn = null; 235 236 //数据插入时,默认插入数据字段 237 Map<String, String> onInsertColumns = new HashMap<>(); 238 239 //数据更新时,默认更新内容的字段 240 Map<String, String> onUpdateColumns = new HashMap<>(); 241 242 //大字段列 243 Set<String> largeColumns = new LinkedHashSet<>(); 244 245 // 默认查询列 246 Set<String> defaultQueryColumns = new LinkedHashSet<>(); 247 248 List<Field> entityFields = getColumnFields(entityClass); 249 250 FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig(); 251 252 for (Field field : entityFields) { 253 254 Class<?> fieldType = reflector.getGetterType(field.getName()); 255 256 //移除默认的忽略字段 257 boolean isIgnoreField = false; 258 for (Class<?> ignoreColumnType : ignoreColumnTypes) { 259 if (ignoreColumnType.isAssignableFrom(fieldType)) { 260 isIgnoreField = true; 261 break; 262 } 263 } 264 265 if (isIgnoreField) { 266 continue; 267 } 268 269 Column columnAnnotation = field.getAnnotation(Column.class); 270 271 //满足以下 3 种情况,不支持该类型 272 if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler 273 && !fieldType.isEnum() // 类型不是枚举 274 && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型 275 ) { 276 // 忽略 集合 实体类 解析 277 if (columnAnnotation != null && columnAnnotation.ignore()) { 278 continue; 279 } 280 // 集合嵌套 281 if (Collection.class.isAssignableFrom(fieldType)) { 282 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 283 if (genericType instanceof ParameterizedType) { 284 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 285 if (actualTypeArgument instanceof Class) { 286 tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument); 287 } 288 } 289 } 290 // 实体类嵌套 291 else if (!Map.class.isAssignableFrom(fieldType) 292 && !fieldType.isArray()) { 293 tableInfo.addAssociationType(field.getName(), fieldType); 294 } 295 // 不支持的类型直接跳过 296 continue; 297 } 298 299 //列名 300 String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation); 301 302 //逻辑删除字段 303 if ((columnAnnotation != null && columnAnnotation.isLogicDelete()) 304 || columnName.equals(config.getLogicDeleteColumn())) { 305 if (logicDeleteColumn == null) { 306 logicDeleteColumn = columnName; 307 } else { 308 throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName()); 309 } 310 } 311 312 //乐观锁版本字段 313 if ((columnAnnotation != null && columnAnnotation.version()) 314 || columnName.equals(config.getVersionColumn())) { 315 if (versionColumn == null) { 316 versionColumn = columnName; 317 } else { 318 throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName()); 319 } 320 } 321 322 //租户ID 字段 323 if ((columnAnnotation != null && columnAnnotation.tenantId()) 324 || columnName.equals(config.getTenantColumn())) { 325 if (tenantIdColumn == null) { 326 tenantIdColumn = columnName; 327 } else { 328 throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName()); 329 } 330 } 331 332 333 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) { 334 onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim()); 335 } 336 337 338 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) { 339 onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim()); 340 } 341 342 343 if (columnAnnotation != null && columnAnnotation.isLarge()) { 344 largeColumns.add(columnName); 345 } 346 347 //主键配置 348 Id id = field.getAnnotation(Id.class); 349 ColumnInfo columnInfo; 350 if (id != null) { 351 columnInfo = new IdInfo(id); 352 idInfos.add((IdInfo) columnInfo); 353 } else { 354 columnInfo = new ColumnInfo(); 355 columnInfoList.add(columnInfo); 356 } 357 358 ColumnAlias columnAlias = null; 359 360 // 属性上没有别名,查找 getter 方法上有没有别名 361 Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName())); 362 if (getterMethod != null) { 363 columnAlias = getterMethod.getAnnotation(ColumnAlias.class); 364 } 365 366 if (columnAlias == null) { 367 columnAlias = field.getAnnotation(ColumnAlias.class); 368 } 369 370 if (columnAlias != null) { 371 columnInfo.setAlias(columnAlias.value()); 372 } 373 374 columnInfo.setColumn(columnName); 375 columnInfo.setProperty(field.getName()); 376 columnInfo.setPropertyType(fieldType); 377 columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore()); 378 379 380 // 默认查询列 没有忽略且不是大字段 381 if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) { 382 defaultQueryColumns.add(columnName); 383 } 384 385 386 //typeHandler 配置 387 if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) { 388 TypeHandler<?> typeHandler; 389 390 //集合类型,支持泛型 391 //fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE 392 if (Collection.class.isAssignableFrom(fieldType)) { 393 typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType); 394 } 395 396 //非集合类型 397 else { 398 Class<?> typeHandlerClass = columnAnnotation.typeHandler(); 399 Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration(); 400 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 401 typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass); 402 } 403 404 columnInfo.setTypeHandler(typeHandler); 405 } 406 407 // 数据脱敏配置 408 ColumnMask columnMask = field.getAnnotation(ColumnMask.class); 409 if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) { 410 if (String.class != fieldType) { 411 throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName()); 412 } 413 columnInfo.setMaskType(columnMask.value().trim()); 414 } 415 416 // jdbcType 配置 417 if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) { 418 columnInfo.setJdbcType(columnAnnotation.jdbcType()); 419 } 420 421 } 422 423 424 tableInfo.setLogicDeleteColumn(logicDeleteColumn); 425 tableInfo.setVersionColumn(versionColumn); 426 tableInfo.setTenantIdColumn(tenantIdColumn); 427 428 if (!onInsertColumns.isEmpty()) { 429 tableInfo.setOnInsertColumns(onInsertColumns); 430 } 431 432 if (!onUpdateColumns.isEmpty()) { 433 tableInfo.setOnUpdateColumns(onUpdateColumns); 434 } 435 436 if (!largeColumns.isEmpty()) { 437 tableInfo.setLargeColumns(largeColumns.toArray(new String[0])); 438 } 439 440 if (!defaultQueryColumns.isEmpty()) { 441 tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0])); 442 } 443 444 // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column, 445 // 否则会影响 SQL 的字段构建顺序 446 tableInfo.setPrimaryKeyList(idInfos); 447 tableInfo.setColumnInfoList(columnInfoList); 448 449 450 return tableInfo; 451 } 452 453 /** 454 * 创建 typeHandler 455 * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)} 456 * 457 * @param entityClass 458 * @param field 459 * @param typeHandlerClass 460 * @param fieldType 461 */ 462 private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) { 463 Class<?> genericClass = null; 464 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 465 if (genericType instanceof ParameterizedType) { 466 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 467 if (actualTypeArgument instanceof Class) { 468 genericClass = (Class<?>) actualTypeArgument; 469 } 470 } 471 472 try { 473 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class); 474 return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass); 475 } catch (NoSuchMethodException ignored) { 476 } catch (Exception e) { 477 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 478 } 479 try { 480 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class); 481 return (TypeHandler<?>) constructor.newInstance(fieldType); 482 } catch (NoSuchMethodException ignored) { 483 } catch (Exception e) { 484 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 485 } 486 try { 487 Constructor<?> c = typeHandlerClass.getConstructor(); 488 return (TypeHandler<?>) c.newInstance(); 489 } catch (Exception e) { 490 throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); 491 } 492 } 493 494 495 static String getColumnName(boolean isCamelToUnderline, Field field, Column column) { 496 if (column != null && StringUtil.isNotBlank(column.value())) { 497 return column.value(); 498 } 499 if (isCamelToUnderline) { 500 return StringUtil.camelToUnderline(field.getName()); 501 } 502 return field.getName(); 503 } 504 505 506 public static List<Field> getColumnFields(Class<?> entityClass) { 507 List<Field> fields = new ArrayList<>(); 508 doGetFields(entityClass, fields); 509 return fields; 510 } 511 512 513 private static void doGetFields(Class<?> entityClass, List<Field> fields) { 514 if (entityClass == null || entityClass == Object.class) { 515 return; 516 } 517 518 Field[] declaredFields = entityClass.getDeclaredFields(); 519 for (Field declaredField : declaredFields) { 520 if (Modifier.isStatic(declaredField.getModifiers()) 521 || existName(fields, declaredField)) { 522 continue; 523 } 524 fields.add(declaredField); 525 } 526 527 doGetFields(entityClass.getSuperclass(), fields); 528 } 529 530 531 private static boolean existName(List<Field> fields, Field field) { 532 for (Field f : fields) { 533 if (f.getName().equalsIgnoreCase(field.getName())) { 534 return true; 535 } 536 } 537 return false; 538 } 539 540}