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}