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.FlexConsts; 021import com.mybatisflex.core.FlexGlobalConfig; 022import com.mybatisflex.core.exception.FlexExceptions; 023import com.mybatisflex.core.util.ClassUtil; 024import com.mybatisflex.core.util.CollectionUtil; 025import com.mybatisflex.core.util.Reflectors; 026import com.mybatisflex.core.util.StringUtil; 027import org.apache.ibatis.io.ResolverUtil; 028import org.apache.ibatis.reflection.Reflector; 029import org.apache.ibatis.reflection.TypeParameterResolver; 030import org.apache.ibatis.session.Configuration; 031import org.apache.ibatis.type.JdbcType; 032import org.apache.ibatis.type.TypeHandler; 033import org.apache.ibatis.type.TypeHandlerRegistry; 034import org.apache.ibatis.type.UnknownTypeHandler; 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 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 069 private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>(); 070 private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>(); 071 private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>(); 072 private static final Set<String> initedPackageNames = new HashSet<>(); 073 074 075 public synchronized static void init(String mapperPackageName) { 076 if (!initedPackageNames.contains(mapperPackageName)) { 077 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); 078 resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName); 079 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 080 for (Class<? extends Class<?>> mapperClass : mapperSet) { 081 ofMapperClass(mapperClass); 082 } 083 initedPackageNames.add(mapperPackageName); 084 } 085 } 086 087 088 public static TableInfo ofMapperClass(Class<?> mapperClass) { 089 return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> { 090 Class<?> entityClass = getEntityClass(mapperClass); 091 if (entityClass == null) { 092 return null; 093 } 094 return ofEntityClass(entityClass); 095 }); 096 } 097 098 099 public static TableInfo ofEntityClass(Class<?> entityClass) { 100 return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> { 101 TableInfo tableInfo = createTableInfo(entityClass); 102 tableInfoMap.put(tableInfo.getTableNameWithSchema(), tableInfo); 103 return tableInfo; 104 }); 105 } 106 107 108 public static TableInfo ofTableName(String tableName) { 109 return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null; 110 } 111 112 113 private static Class<?> getEntityClass(Class<?> mapperClass) { 114 if (mapperClass == null || mapperClass == Object.class) { 115 return null; 116 } 117 Type[] genericInterfaces = mapperClass.getGenericInterfaces(); 118 if (genericInterfaces.length == 1) { 119 Type type = genericInterfaces[0]; 120 if (type instanceof ParameterizedType) { 121 Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0]; 122 return actualTypeArgument instanceof Class ? (Class<?>) actualTypeArgument : null; 123 } else if (type instanceof Class) { 124 return getEntityClass((Class<?>) type); 125 } 126 } 127 return getEntityClass(mapperClass.getSuperclass()); 128 } 129 130 131 private static TableInfo createTableInfo(Class<?> entityClass) { 132 133 TableInfo tableInfo = new TableInfo(); 134 tableInfo.setEntityClass(entityClass); 135 Reflector reflector = Reflectors.of(entityClass); 136 tableInfo.setReflector(reflector); 137 138 //初始化表名 139 Table table = entityClass.getAnnotation(Table.class); 140 if (table != null) { 141 tableInfo.setTableName(table.value()); 142 tableInfo.setSchema(table.schema()); 143 tableInfo.setCamelToUnderline(table.camelToUnderline()); 144 145 if (table.onInsert().length > 0) { 146 List<InsertListener> insertListeners = Arrays.stream(table.onInsert()) 147 .filter(listener -> listener != NoneListener.class) 148 .map(ClassUtil::newInstance) 149 .collect(Collectors.toList()); 150 tableInfo.setOnInsertListeners(insertListeners); 151 } 152 153 if (table.onUpdate().length > 0) { 154 List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate()) 155 .filter(listener -> listener != NoneListener.class) 156 .map(ClassUtil::newInstance) 157 .collect(Collectors.toList()); 158 tableInfo.setOnUpdateListeners(updateListeners); 159 } 160 161 if (table.onSet().length > 0) { 162 List<SetListener> setListeners = Arrays.stream(table.onSet()) 163 .filter(listener -> listener != NoneListener.class) 164 .map(ClassUtil::newInstance) 165 .collect(Collectors.toList()); 166 tableInfo.setOnSetListeners(setListeners); 167 } 168 169 if (StringUtil.isNotBlank(table.dataSource())) { 170 tableInfo.setDataSource(table.dataSource()); 171 } 172 } else { 173 //默认为类名转驼峰下划线 174 String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName()); 175 tableInfo.setTableName(tableName); 176 } 177 178 //初始化字段相关 179 List<ColumnInfo> columnInfoList = new ArrayList<>(); 180 List<IdInfo> idInfos = new ArrayList<>(); 181 182 Field idField = null; 183 184 String logicDeleteColumn = null; 185 String versionColumn = null; 186 String tenantIdColumn = null; 187 188 //数据插入时,默认插入数据字段 189 Map<String, String> onInsertColumns = new HashMap<>(); 190 191 //数据更新时,默认更新内容的字段 192 Map<String, String> onUpdateColumns = new HashMap<>(); 193 194 //大字段列 195 Set<String> largeColumns = new LinkedHashSet<>(); 196 197 // 默认查询列 198 Set<String> defaultQueryColumns = new LinkedHashSet<>(); 199 200 201 List<Field> entityFields = getColumnFields(entityClass); 202 203 for (Field field : entityFields) { 204 205 Column column = field.getAnnotation(Column.class); 206 if (column != null && column.ignore()) { 207 continue; // ignore 208 } 209 210 Class<?> fieldType = reflector.getGetterType(field.getName()); 211 212 //满足以下 3 种情况,不支持该类型 213 if ((column == null || column.typeHandler() == UnknownTypeHandler.class) // 未配置 typeHandler 214 && !fieldType.isEnum() // 类型不是枚举 215 && !defaultSupportColumnTypes.contains(fieldType) //默认的自动类型不包含该类型 216 ) { 217 // 集合嵌套 218 if (Collection.class.isAssignableFrom(fieldType)) { 219 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 220 if (genericType instanceof ParameterizedType) { 221 Class<?> actualTypeArgument = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0]; 222 tableInfo.addCollectionType(field, actualTypeArgument); 223 } 224 } 225 // 实体类嵌套 226 else if (!Map.class.isAssignableFrom(fieldType) 227 && !fieldType.isArray()) { 228 tableInfo.addAssociationType(field.getName(), fieldType); 229 } 230 // 不支持的类型直接跳过 231 continue; 232 } 233 234 //列名 235 String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, column); 236 237 //逻辑删除字段 238 if (column != null && column.isLogicDelete()) { 239 if (logicDeleteColumn == null) { 240 logicDeleteColumn = columnName; 241 } else { 242 throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName()); 243 } 244 } 245 246 //乐观锁版本字段 247 if (column != null && column.version()) { 248 if (versionColumn == null) { 249 versionColumn = columnName; 250 } else { 251 throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName()); 252 } 253 } 254 255 //租户ID 字段 256 if (column != null && column.tenantId()) { 257 if (tenantIdColumn == null) { 258 tenantIdColumn = columnName; 259 } else { 260 throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName()); 261 } 262 } 263 264 if (column != null && StringUtil.isNotBlank(column.onInsertValue())) { 265 onInsertColumns.put(columnName, column.onInsertValue().trim()); 266 } 267 268 269 if (column != null && StringUtil.isNotBlank(column.onUpdateValue())) { 270 onUpdateColumns.put(columnName, column.onUpdateValue().trim()); 271 } 272 273 274 if (column != null && column.isLarge()) { 275 largeColumns.add(columnName); 276 } 277 278 if (column == null || !column.isLarge()) { 279 defaultQueryColumns.add(columnName); 280 } 281 282 Id id = field.getAnnotation(Id.class); 283 ColumnInfo columnInfo; 284 if (id != null) { 285 columnInfo = new IdInfo(id); 286 idInfos.add((IdInfo) columnInfo); 287 } else { 288 columnInfo = new ColumnInfo(); 289 columnInfoList.add(columnInfo); 290 } 291 292 ColumnAlias columnAlias = null; 293 // 属性上没有别名,查找 getter 方法上有没有别名 294 Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName())); 295 if (getterMethod != null) { 296 columnAlias = getterMethod.getAnnotation(ColumnAlias.class); 297 } 298 299 if (columnAlias == null) { 300 columnAlias = field.getAnnotation(ColumnAlias.class); 301 } 302 303 if (columnAlias != null) { 304 columnInfo.setAlias(columnAlias.value()); 305 } 306 307 columnInfo.setColumn(columnName); 308 columnInfo.setProperty(field.getName()); 309 columnInfo.setPropertyType(fieldType); 310 311 if (column != null && column.typeHandler() != UnknownTypeHandler.class) { 312 Class<?> typeHandlerClass = column.typeHandler(); 313 Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration(); 314 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 315 TypeHandler<?> typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass); 316 columnInfo.setTypeHandler(typeHandler); 317 } 318 319 ColumnMask columnMask = field.getAnnotation(ColumnMask.class); 320 if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) { 321 if (String.class != fieldType) { 322 throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName()); 323 } 324 columnInfo.setMaskType(columnMask.value().trim()); 325 } 326 327 if (column != null && column.jdbcType() != JdbcType.UNDEFINED) { 328 columnInfo.setJdbcType(column.jdbcType()); 329 } 330 331 if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(field.getName())) { 332 idField = field; 333 } 334 } 335 336 337 if (idInfos.isEmpty() && idField != null) { 338 int index = -1; 339 for (int i = 0; i < columnInfoList.size(); i++) { 340 ColumnInfo columnInfo = columnInfoList.get(i); 341 if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(columnInfo.getProperty())) { 342 index = i; 343 break; 344 } 345 } 346 if (index >= 0) { 347 ColumnInfo removedColumnInfo = columnInfoList.remove(index); 348 idInfos.add(new IdInfo(removedColumnInfo)); 349 } 350 } 351 352 tableInfo.setLogicDeleteColumn(logicDeleteColumn); 353 tableInfo.setVersionColumn(versionColumn); 354 tableInfo.setTenantIdColumn(tenantIdColumn); 355 356 if (!onInsertColumns.isEmpty()) { 357 tableInfo.setOnInsertColumns(onInsertColumns); 358 } 359 360 if (!onUpdateColumns.isEmpty()) { 361 tableInfo.setOnUpdateColumns(onUpdateColumns); 362 } 363 364 if (!largeColumns.isEmpty()) { 365 tableInfo.setLargeColumns(largeColumns.toArray(new String[0])); 366 } 367 368 if (!defaultQueryColumns.isEmpty()) { 369 tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0])); 370 } 371 372 tableInfo.setPrimaryKeyList(idInfos); 373 tableInfo.setColumnInfoList(columnInfoList); 374 375 376 return tableInfo; 377 } 378 379 static String getColumnName(boolean isCamelToUnderline, Field field, Column column) { 380 if (column != null && StringUtil.isNotBlank(column.value())) { 381 return column.value(); 382 } 383 if (isCamelToUnderline) { 384 return StringUtil.camelToUnderline(field.getName()); 385 } 386 return field.getName(); 387 } 388 389 390 public static List<Field> getColumnFields(Class<?> entityClass) { 391 List<Field> fields = new ArrayList<>(); 392 doGetFields(entityClass, fields); 393 return fields; 394 } 395 396 397 private static void doGetFields(Class<?> entityClass, List<Field> fields) { 398 if (entityClass == null || entityClass == Object.class) { 399 return; 400 } 401 402 Field[] declaredFields = entityClass.getDeclaredFields(); 403 for (Field declaredField : declaredFields) { 404 if (Modifier.isStatic(declaredField.getModifiers()) 405 || existName(fields, declaredField)) { 406 continue; 407 } 408 fields.add(declaredField); 409 } 410 411 doGetFields(entityClass.getSuperclass(), fields); 412 } 413 414 415 private static boolean existName(List<Field> fields, Field field) { 416 for (Field f : fields) { 417 if (f.getName().equalsIgnoreCase(field.getName())) { 418 return true; 419 } 420 } 421 return false; 422 } 423 424}