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