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            if (Modifier.isStatic(declaredField.getModifiers())
568                || existName(fields, declaredField)) {
569                continue;
570            }
571            fields.add(declaredField);
572        }
573
574        doGetFields(entityClass.getSuperclass(), fields);
575    }
576
577
578    private static boolean existName(List<Field> fields, Field field) {
579        for (Field f : fields) {
580            if (f.getName().equalsIgnoreCase(field.getName())) {
581                return true;
582            }
583        }
584        return false;
585    }
586
587}