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 Type[] genericInterfaces = mapperClass.getGenericInterfaces(); 122 if (genericInterfaces.length == 1) { 123 Type type = genericInterfaces[0]; 124 if (type instanceof ParameterizedType) { 125 Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0]; 126 return actualTypeArgument instanceof Class ? (Class<?>) actualTypeArgument : null; 127 } else if (type instanceof Class) { 128 return getEntityClass((Class<?>) type); 129 } 130 } 131 return getEntityClass(mapperClass.getSuperclass()); 132 } 133 134 135 private static TableInfo createTableInfo(Class<?> entityClass) { 136 137 TableInfo tableInfo = new TableInfo(); 138 tableInfo.setEntityClass(entityClass); 139 Reflector reflector = Reflectors.of(entityClass); 140 tableInfo.setReflector(reflector); 141 142 //初始化表名 143 Table table = entityClass.getAnnotation(Table.class); 144 if (table != null) { 145 tableInfo.setSchema(table.schema()); 146 tableInfo.setTableName(table.value()); 147 tableInfo.setCamelToUnderline(table.camelToUnderline()); 148 149 if (table.onInsert().length > 0) { 150 List<InsertListener> insertListeners = Arrays.stream(table.onInsert()) 151 .filter(listener -> listener != NoneListener.class) 152 .map(ClassUtil::newInstance) 153 .collect(Collectors.toList()); 154 tableInfo.setOnInsertListeners(insertListeners); 155 } 156 157 if (table.onUpdate().length > 0) { 158 List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate()) 159 .filter(listener -> listener != NoneListener.class) 160 .map(ClassUtil::newInstance) 161 .collect(Collectors.toList()); 162 tableInfo.setOnUpdateListeners(updateListeners); 163 } 164 165 if (table.onSet().length > 0) { 166 List<SetListener> setListeners = Arrays.stream(table.onSet()) 167 .filter(listener -> listener != NoneListener.class) 168 .map(ClassUtil::newInstance) 169 .collect(Collectors.toList()); 170 tableInfo.setOnSetListeners(setListeners); 171 } 172 173 if (StringUtil.isNotBlank(table.dataSource())) { 174 tableInfo.setDataSource(table.dataSource()); 175 } 176 } else { 177 //默认为类名转驼峰下划线 178 String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName()); 179 tableInfo.setTableName(tableName); 180 } 181 182 //初始化字段相关 183 List<ColumnInfo> columnInfoList = new ArrayList<>(); 184 List<IdInfo> idInfos = new ArrayList<>(); 185 186 187 String logicDeleteColumn = null; 188 String versionColumn = null; 189 String tenantIdColumn = null; 190 191 //数据插入时,默认插入数据字段 192 Map<String, String> onInsertColumns = new HashMap<>(); 193 194 //数据更新时,默认更新内容的字段 195 Map<String, String> onUpdateColumns = new HashMap<>(); 196 197 //大字段列 198 Set<String> largeColumns = new LinkedHashSet<>(); 199 200 // 默认查询列 201 Set<String> defaultQueryColumns = new LinkedHashSet<>(); 202 203 List<Field> entityFields = getColumnFields(entityClass); 204 205 FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig(); 206 207 for (Field field : entityFields) { 208 209 Class<?> fieldType = reflector.getGetterType(field.getName()); 210 211 //移除默认的忽略字段 212 boolean isIgnoreField = false; 213 for (Class<?> ignoreColumnType : ignoreColumnTypes) { 214 if (ignoreColumnType.isAssignableFrom(fieldType)) { 215 isIgnoreField = true; 216 break; 217 } 218 } 219 220 if (isIgnoreField) { 221 continue; 222 } 223 224 Column columnAnnotation = field.getAnnotation(Column.class); 225 226 //满足以下 3 种情况,不支持该类型 227 if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler 228 && !fieldType.isEnum() // 类型不是枚举 229 && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型 230 ) { 231 // 忽略 集合 实体类 解析 232 if (columnAnnotation != null && columnAnnotation.ignore()) { 233 continue; 234 } 235 // 集合嵌套 236 if (Collection.class.isAssignableFrom(fieldType)) { 237 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 238 if (genericType instanceof ParameterizedType) { 239 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 240 if (actualTypeArgument instanceof Class) { 241 tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument); 242 } 243 } 244 } 245 // 实体类嵌套 246 else if (!Map.class.isAssignableFrom(fieldType) 247 && !fieldType.isArray()) { 248 tableInfo.addAssociationType(field.getName(), fieldType); 249 } 250 // 不支持的类型直接跳过 251 continue; 252 } 253 254 //列名 255 String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation); 256 257 //逻辑删除字段 258 if ((columnAnnotation != null && columnAnnotation.isLogicDelete()) 259 || columnName.equals(config.getLogicDeleteColumn())) { 260 if (logicDeleteColumn == null) { 261 logicDeleteColumn = columnName; 262 } else { 263 throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName()); 264 } 265 } 266 267 //乐观锁版本字段 268 if ((columnAnnotation != null && columnAnnotation.version()) 269 || columnName.equals(config.getVersionColumn())) { 270 if (versionColumn == null) { 271 versionColumn = columnName; 272 } else { 273 throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName()); 274 } 275 } 276 277 //租户ID 字段 278 if ((columnAnnotation != null && columnAnnotation.tenantId()) 279 || columnName.equals(config.getTenantColumn())) { 280 if (tenantIdColumn == null) { 281 tenantIdColumn = columnName; 282 } else { 283 throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName()); 284 } 285 } 286 287 288 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) { 289 onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim()); 290 } 291 292 293 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) { 294 onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim()); 295 } 296 297 298 if (columnAnnotation != null && columnAnnotation.isLarge()) { 299 largeColumns.add(columnName); 300 } 301 302 //主键配置 303 Id id = field.getAnnotation(Id.class); 304 ColumnInfo columnInfo; 305 if (id != null) { 306 columnInfo = new IdInfo(id); 307 idInfos.add((IdInfo) columnInfo); 308 } else { 309 columnInfo = new ColumnInfo(); 310 columnInfoList.add(columnInfo); 311 } 312 313 ColumnAlias columnAlias = null; 314 315 // 属性上没有别名,查找 getter 方法上有没有别名 316 Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName())); 317 if (getterMethod != null) { 318 columnAlias = getterMethod.getAnnotation(ColumnAlias.class); 319 } 320 321 if (columnAlias == null) { 322 columnAlias = field.getAnnotation(ColumnAlias.class); 323 } 324 325 if (columnAlias != null) { 326 columnInfo.setAlias(columnAlias.value()); 327 } 328 329 columnInfo.setColumn(columnName); 330 columnInfo.setProperty(field.getName()); 331 columnInfo.setPropertyType(fieldType); 332 columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore()); 333 334 335 // 默认查询列 没有忽略且不是大字段 336 if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) { 337 defaultQueryColumns.add(columnName); 338 } 339 340 341 //typeHandler 配置 342 if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) { 343 TypeHandler<?> typeHandler; 344 345 //集合类型,支持泛型 346 //fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE 347 if (Collection.class.isAssignableFrom(fieldType)) { 348 typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType); 349 } 350 351 //非集合类型 352 else { 353 Class<?> typeHandlerClass = columnAnnotation.typeHandler(); 354 Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration(); 355 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 356 typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass); 357 } 358 359 columnInfo.setTypeHandler(typeHandler); 360 } 361 362 // 数据脱敏配置 363 ColumnMask columnMask = field.getAnnotation(ColumnMask.class); 364 if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) { 365 if (String.class != fieldType) { 366 throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName()); 367 } 368 columnInfo.setMaskType(columnMask.value().trim()); 369 } 370 371 // jdbcType 配置 372 if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) { 373 columnInfo.setJdbcType(columnAnnotation.jdbcType()); 374 } 375 376 } 377 378 379 tableInfo.setLogicDeleteColumn(logicDeleteColumn); 380 tableInfo.setVersionColumn(versionColumn); 381 tableInfo.setTenantIdColumn(tenantIdColumn); 382 383 if (!onInsertColumns.isEmpty()) { 384 tableInfo.setOnInsertColumns(onInsertColumns); 385 } 386 387 if (!onUpdateColumns.isEmpty()) { 388 tableInfo.setOnUpdateColumns(onUpdateColumns); 389 } 390 391 if (!largeColumns.isEmpty()) { 392 tableInfo.setLargeColumns(largeColumns.toArray(new String[0])); 393 } 394 395 if (!defaultQueryColumns.isEmpty()) { 396 tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0])); 397 } 398 399 // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column, 400 // 否则会影响 SQL 的字段构建顺序 401 tableInfo.setPrimaryKeyList(idInfos); 402 tableInfo.setColumnInfoList(columnInfoList); 403 404 405 return tableInfo; 406 } 407 408 /** 409 * 创建 typeHandler 410 * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)} 411 * 412 * @param entityClass 413 * @param field 414 * @param typeHandlerClass 415 * @param fieldType 416 */ 417 private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) { 418 Class<?> genericClass = null; 419 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 420 if (genericType instanceof ParameterizedType) { 421 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 422 if (actualTypeArgument instanceof Class) { 423 genericClass = (Class<?>) actualTypeArgument; 424 } 425 } 426 427 try { 428 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class); 429 return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass); 430 } catch (NoSuchMethodException ignored) { 431 } catch (Exception e) { 432 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 433 } 434 try { 435 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class); 436 return (TypeHandler<?>) constructor.newInstance(fieldType); 437 } catch (NoSuchMethodException ignored) { 438 } catch (Exception e) { 439 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 440 } 441 try { 442 Constructor<?> c = typeHandlerClass.getConstructor(); 443 return (TypeHandler<?>) c.newInstance(); 444 } catch (Exception e) { 445 throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); 446 } 447 } 448 449 450 static String getColumnName(boolean isCamelToUnderline, Field field, Column column) { 451 if (column != null && StringUtil.isNotBlank(column.value())) { 452 return column.value(); 453 } 454 if (isCamelToUnderline) { 455 return StringUtil.camelToUnderline(field.getName()); 456 } 457 return field.getName(); 458 } 459 460 461 public static List<Field> getColumnFields(Class<?> entityClass) { 462 List<Field> fields = new ArrayList<>(); 463 doGetFields(entityClass, fields); 464 return fields; 465 } 466 467 468 private static void doGetFields(Class<?> entityClass, List<Field> fields) { 469 if (entityClass == null || entityClass == Object.class) { 470 return; 471 } 472 473 Field[] declaredFields = entityClass.getDeclaredFields(); 474 for (Field declaredField : declaredFields) { 475 if (Modifier.isStatic(declaredField.getModifiers()) 476 || existName(fields, declaredField)) { 477 continue; 478 } 479 fields.add(declaredField); 480 } 481 482 doGetFields(entityClass.getSuperclass(), fields); 483 } 484 485 486 private static boolean existName(List<Field> fields, Field field) { 487 for (Field f : fields) { 488 if (f.getName().equalsIgnoreCase(field.getName())) { 489 return true; 490 } 491 } 492 return false; 493 } 494 495}