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