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.session.Configuration; 042import org.apache.ibatis.type.JdbcType; 043import org.apache.ibatis.type.TypeException; 044import org.apache.ibatis.type.TypeHandler; 045import org.apache.ibatis.type.TypeHandlerRegistry; 046import org.apache.ibatis.type.UnknownTypeHandler; 047import org.apache.ibatis.util.MapUtil; 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 tableInfoMap.put(tableInfo.getTableNameWithSchema(), tableInfo); 144 return tableInfo; 145 }); 146 } 147 148 149 public static TableInfo ofTableName(String tableName) { 150 return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null; 151 } 152 153 154 private static Class<?> getEntityClass(Class<?> mapperClass) { 155 if (mapperClass == null || mapperClass == Object.class) { 156 return null; 157 } 158 return getEntityClass(mapperClass, null); 159 } 160 161 private static Class<?> getEntityClass(Class<?> mapperClass, Type[] actualTypeArguments) { 162 // 检查基接口 163 Type[] genericInterfaces = mapperClass.getGenericInterfaces(); 164 for (Type type : genericInterfaces) { 165 if (type instanceof ParameterizedType) { 166 // 泛型基接口 167 ParameterizedType parameterizedType = (ParameterizedType) type; 168 Type rawType = parameterizedType.getRawType(); 169 Type[] typeArguments = parameterizedType.getActualTypeArguments(); 170 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 171 if (rawType == BaseMapper.class) { 172 // 找到了 173 if (typeArguments[0] instanceof Class) { 174 return (Class<?>) typeArguments[0]; 175 } 176 } else if (rawType instanceof Class) { 177 // 其他泛型基接口 178 Class<?> entityClass = getEntityClass((Class<?>) rawType, typeArguments); 179 if (entityClass != null) { 180 return entityClass; 181 } 182 } 183 } else if (type instanceof Class) { 184 // 其他基接口 185 Class<?> entityClass = getEntityClass((Class<?>) type); 186 if (entityClass != null) { 187 return entityClass; 188 } 189 } 190 } 191 // 检查基类 192 Class<?> superclass = mapperClass.getSuperclass(); 193 if (superclass == null || superclass == Object.class) { 194 return null; 195 } 196 Type[] typeArguments = superclass.getTypeParameters(); 197 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 198 return getEntityClass(superclass, typeArguments); 199 } 200 201 private static void adjustTypeArguments(Class<?> subclass, Type[] subclassTypeArguments, Type[] typeArguments) { 202 for (int i = 0; i < typeArguments.length; i++) { 203 if (typeArguments[i] instanceof TypeVariable) { 204 TypeVariable<?> typeVariable = (TypeVariable<?>) typeArguments[i]; 205 TypeVariable<?>[] typeParameters = subclass.getTypeParameters(); 206 for (int j = 0; j < typeParameters.length; j++) { 207 if (Objects.equals(typeVariable.getName(), typeParameters[j].getName())) { 208 typeArguments[i] = subclassTypeArguments[j]; 209 break; 210 } 211 } 212 } 213 } 214 } 215 216 217 private static TableInfo createTableInfo(Class<?> entityClass) { 218 219 TableInfo tableInfo = new TableInfo(); 220 tableInfo.setEntityClass(entityClass); 221 Reflector reflector = Reflectors.of(entityClass); 222 tableInfo.setReflector(reflector); 223 224 //初始化表名 225 Table table = entityClass.getAnnotation(Table.class); 226 if (table != null) { 227 tableInfo.setSchema(table.schema()); 228 tableInfo.setTableName(table.value()); 229 tableInfo.setCamelToUnderline(table.camelToUnderline()); 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 for (Field field : entityFields) { 290 291 Class<?> fieldType = reflector.getGetterType(field.getName()); 292 293 //移除默认的忽略字段 294 boolean isIgnoreField = false; 295 for (Class<?> ignoreColumnType : ignoreColumnTypes) { 296 if (ignoreColumnType.isAssignableFrom(fieldType)) { 297 isIgnoreField = true; 298 break; 299 } 300 } 301 302 if (isIgnoreField) { 303 continue; 304 } 305 306 Column columnAnnotation = field.getAnnotation(Column.class); 307 308 309 //满足以下 3 种情况,不支持该类型的属性自动映射为字段 310 if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler 311 && !fieldType.isEnum() // 类型不是枚举 312 && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型 313 ) { 314 // 忽略 集合 实体类 解析 315 if (columnAnnotation != null && columnAnnotation.ignore()) { 316 continue; 317 } 318 // 集合嵌套 319 if (Collection.class.isAssignableFrom(fieldType)) { 320 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 321 if (genericType instanceof ParameterizedType) { 322 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 323 if (actualTypeArgument instanceof Class) { 324 tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument); 325 } 326 } 327 } 328 // 实体类嵌套 329 else if (!Map.class.isAssignableFrom(fieldType) 330 && !fieldType.isArray()) { 331 tableInfo.addAssociationType(field.getName(), fieldType); 332 } 333 // 不支持的类型直接跳过 334 continue; 335 } 336 337 //列名 338 String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation); 339 340 //逻辑删除字段 341 if ((columnAnnotation != null && columnAnnotation.isLogicDelete()) 342 || columnName.equals(config.getLogicDeleteColumn())) { 343 if (logicDeleteColumn == null) { 344 logicDeleteColumn = columnName; 345 } else { 346 throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName()); 347 } 348 } 349 350 //乐观锁版本字段 351 if ((columnAnnotation != null && columnAnnotation.version()) 352 || columnName.equals(config.getVersionColumn())) { 353 if (versionColumn == null) { 354 versionColumn = columnName; 355 } else { 356 throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName()); 357 } 358 } 359 360 //租户ID 字段 361 if ((columnAnnotation != null && columnAnnotation.tenantId()) 362 || columnName.equals(config.getTenantColumn())) { 363 if (tenantIdColumn == null) { 364 tenantIdColumn = columnName; 365 } else { 366 throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName()); 367 } 368 } 369 370 371 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) { 372 onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim()); 373 } 374 375 376 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) { 377 onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim()); 378 } 379 380 381 if (columnAnnotation != null && columnAnnotation.isLarge()) { 382 largeColumns.add(columnName); 383 } 384 385 //主键配置 386 Id id = field.getAnnotation(Id.class); 387 ColumnInfo columnInfo; 388 if (id != null) { 389 columnInfo = new IdInfo(id); 390 idInfos.add((IdInfo) columnInfo); 391 } else { 392 columnInfo = new ColumnInfo(); 393 columnInfoList.add(columnInfo); 394 } 395 396 ColumnAlias columnAlias = null; 397 398 // 属性上没有别名,查找 getter 方法上有没有别名 399 Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName())); 400 if (getterMethod != null) { 401 columnAlias = getterMethod.getAnnotation(ColumnAlias.class); 402 } 403 404 if (columnAlias == null) { 405 columnAlias = field.getAnnotation(ColumnAlias.class); 406 } 407 408 if (columnAlias != null) { 409 columnInfo.setAlias(columnAlias.value()); 410 } 411 412 columnInfo.setColumn(columnName); 413 columnInfo.setProperty(field.getName()); 414 columnInfo.setPropertyType(fieldType); 415 columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore()); 416 417 418 // 默认查询列 没有忽略且不是大字段 419 if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) { 420 defaultQueryColumns.add(columnName); 421 } 422 423 424 //typeHandler 配置 425 if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) { 426 TypeHandler<?> typeHandler = null; 427 428 //集合类型,支持泛型 429 //fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE 430 if (Collection.class.isAssignableFrom(fieldType)) { 431 typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType); 432 } 433 434 //非集合类型 435 else { 436 Class<?> typeHandlerClass = columnAnnotation.typeHandler(); 437 Configuration configuration = config.getConfiguration(); 438 if (configuration != null) { 439 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 440 Class<?> propertyType = columnInfo.getPropertyType(); 441 JdbcType jdbcType = columnAnnotation.jdbcType(); 442 if (jdbcType != JdbcType.UNDEFINED) { 443 typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType); 444 } 445 if (typeHandler == null || !typeHandlerClass.isAssignableFrom(typeHandler.getClass())) { 446 typeHandler = typeHandlerRegistry.getInstance(propertyType, typeHandlerClass); 447 } 448 } 449 } 450 451 columnInfo.setTypeHandler(typeHandler); 452 } 453 454 // 数据脱敏配置 455 ColumnMask columnMask = field.getAnnotation(ColumnMask.class); 456 if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) { 457 if (String.class != fieldType) { 458 throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName()); 459 } 460 columnInfo.setMaskType(columnMask.value().trim()); 461 } 462 463 // jdbcType 配置 464 if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) { 465 columnInfo.setJdbcType(columnAnnotation.jdbcType()); 466 } 467 468 } 469 470 471 tableInfo.setLogicDeleteColumn(logicDeleteColumn); 472 tableInfo.setVersionColumn(versionColumn); 473 tableInfo.setTenantIdColumn(tenantIdColumn); 474 475 if (!onInsertColumns.isEmpty()) { 476 tableInfo.setOnInsertColumns(onInsertColumns); 477 } 478 479 if (!onUpdateColumns.isEmpty()) { 480 tableInfo.setOnUpdateColumns(onUpdateColumns); 481 } 482 483 if (!largeColumns.isEmpty()) { 484 tableInfo.setLargeColumns(largeColumns.toArray(new String[0])); 485 } 486 487 if (!defaultQueryColumns.isEmpty()) { 488 tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0])); 489 } 490 491 // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column, 492 // 否则会影响 SQL 的字段构建顺序 493 tableInfo.setPrimaryKeyList(idInfos); 494 tableInfo.setColumnInfoList(columnInfoList); 495 496 497 return tableInfo; 498 } 499 500 /** 501 * 创建 typeHandler 502 * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)} 503 * 504 * @param entityClass 505 * @param field 506 * @param typeHandlerClass 507 * @param fieldType 508 */ 509 private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) { 510 Class<?> genericClass = null; 511 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 512 if (genericType instanceof ParameterizedType) { 513 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 514 if (actualTypeArgument instanceof Class) { 515 genericClass = (Class<?>) actualTypeArgument; 516 } 517 } 518 519 try { 520 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class); 521 return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass); 522 } catch (NoSuchMethodException ignored) { 523 } catch (Exception e) { 524 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 525 } 526 try { 527 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class); 528 return (TypeHandler<?>) constructor.newInstance(fieldType); 529 } catch (NoSuchMethodException ignored) { 530 } catch (Exception e) { 531 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 532 } 533 try { 534 Constructor<?> c = typeHandlerClass.getConstructor(); 535 return (TypeHandler<?>) c.newInstance(); 536 } catch (Exception e) { 537 throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); 538 } 539 } 540 541 542 static String getColumnName(boolean isCamelToUnderline, Field field, Column column) { 543 if (column != null && StringUtil.isNotBlank(column.value())) { 544 return column.value(); 545 } 546 if (isCamelToUnderline) { 547 return StringUtil.camelToUnderline(field.getName()); 548 } 549 return field.getName(); 550 } 551 552 553 public static List<Field> getColumnFields(Class<?> entityClass) { 554 List<Field> fields = new ArrayList<>(); 555 doGetFields(entityClass, fields); 556 return fields; 557 } 558 559 560 private static void doGetFields(Class<?> entityClass, List<Field> fields) { 561 if (entityClass == null || entityClass == Object.class) { 562 return; 563 } 564 565 Field[] declaredFields = entityClass.getDeclaredFields(); 566 for (Field declaredField : declaredFields) { 567 int modifiers = declaredField.getModifiers(); 568 if (Modifier.isStatic(modifiers) 569 || Modifier.isTransient(modifiers) 570 || existName(fields, declaredField)) { 571 continue; 572 } 573 fields.add(declaredField); 574 } 575 576 doGetFields(entityClass.getSuperclass(), fields); 577 } 578 579 580 private static boolean existName(List<Field> fields, Field field) { 581 for (Field f : fields) { 582 if (f.getName().equalsIgnoreCase(field.getName())) { 583 return true; 584 } 585 } 586 return false; 587 } 588 589}