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