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.FlexConsts;
020import com.mybatisflex.core.FlexGlobalConfig;
021import com.mybatisflex.core.exception.FlexExceptions;
022import com.mybatisflex.core.util.ClassUtil;
023import com.mybatisflex.core.util.CollectionUtil;
024import com.mybatisflex.core.util.StringUtil;
025import org.apache.ibatis.reflection.Reflector;
026import org.apache.ibatis.session.Configuration;
027import org.apache.ibatis.type.JdbcType;
028import org.apache.ibatis.type.TypeHandler;
029import org.apache.ibatis.type.TypeHandlerRegistry;
030import org.apache.ibatis.type.UnknownTypeHandler;
031import org.apache.ibatis.util.MapUtil;
032
033import java.lang.reflect.Field;
034import java.lang.reflect.Modifier;
035import java.lang.reflect.ParameterizedType;
036import java.lang.reflect.Type;
037import java.math.BigDecimal;
038import java.math.BigInteger;
039import java.sql.Time;
040import java.sql.Timestamp;
041import java.time.*;
042import java.time.chrono.JapaneseDate;
043import java.util.*;
044import java.util.concurrent.ConcurrentHashMap;
045
046public class TableInfoFactory {
047
048
049    private static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet(
050            int.class, Integer.class,
051            short.class, Short.class,
052            long.class, Long.class,
053            float.class, Float.class,
054            double.class, Double.class,
055            boolean.class, Boolean.class,
056            Date.class, java.sql.Date.class, Time.class, Timestamp.class,
057            Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class,
058            Year.class, Month.class, YearMonth.class, JapaneseDate.class,
059            byte[].class, Byte[].class,
060            BigInteger.class, BigDecimal.class,
061            char.class, String.class, Character.class
062    );
063
064
065    private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>();
066    private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>();
067    private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>();
068
069
070    public static TableInfo ofMapperClass(Class<?> mapperClass) {
071        return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> {
072            Class<?> entityClass = getEntityClass(mapperClass);
073            if (entityClass == null) {
074                return null;
075            }
076            return ofEntityClass(entityClass);
077        });
078    }
079
080
081    public static TableInfo ofEntityClass(Class<?> entityClass) {
082        return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> {
083            TableInfo tableInfo = createTableInfo(entityClass);
084            tableInfoMap.put(tableInfo.getTableName(), tableInfo);
085            return tableInfo;
086        });
087    }
088
089
090    public static TableInfo ofTableName(String tableName) {
091        return tableInfoMap.get(tableName);
092    }
093
094
095    private static Class<?> getEntityClass(Class<?> mapperClass) {
096        Type[] genericInterfaces = mapperClass.getGenericInterfaces();
097        if (genericInterfaces.length == 1) {
098            Type type = genericInterfaces[0];
099            if (type instanceof ParameterizedType) {
100                return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
101            } else {
102                return getEntityClass((Class<?>) type);
103            }
104        }
105        return null;
106    }
107
108
109    private static TableInfo createTableInfo(Class<?> entityClass) {
110
111        TableInfo tableInfo = new TableInfo();
112        tableInfo.setEntityClass(entityClass);
113        tableInfo.setReflector(new Reflector(entityClass));
114
115
116        //初始化表名
117        Table table = entityClass.getAnnotation(Table.class);
118        if (table != null) {
119            tableInfo.setTableName(table.value());
120            tableInfo.setSchema(table.schema());
121            tableInfo.setCamelToUnderline(table.camelToUnderline());
122
123            if (table.onInsert() != NoneListener.class) {
124                tableInfo.setOnInsertListener(ClassUtil.newInstance(table.onInsert()));
125            }
126
127            if (table.onUpdate() != NoneListener.class) {
128                tableInfo.setOnUpdateListener(ClassUtil.newInstance(table.onUpdate()));
129            }
130
131            if (table.onSet() != NoneListener.class) {
132                tableInfo.setOnSetListener(ClassUtil.newInstance(table.onSet()));
133            }
134
135            if (StringUtil.isNotBlank(table.dataSource())) {
136                tableInfo.setDataSource(table.dataSource());
137            }
138        } else {
139            //默认为类名转驼峰下划线
140            String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName());
141            tableInfo.setTableName(tableName);
142        }
143
144        //初始化字段相关
145        List<ColumnInfo> columnInfoList = new ArrayList<>();
146        List<IdInfo> idInfos = new ArrayList<>();
147
148        Field idField = null;
149
150        String logicDeleteColumn = null;
151        String versionColumn = null;
152        String tenantIdColumn = null;
153
154        //数据插入时,默认插入数据字段
155        Map<String, String> onInsertColumns = new HashMap<>();
156
157        //数据更新时,默认更新内容的字段
158        Map<String, String> onUpdateColumns = new HashMap<>();
159
160        //大字段列
161        Set<String> largeColumns = new LinkedHashSet<>();
162        // 默认查询列
163        Set<String> defaultColumns = new LinkedHashSet<>();
164
165
166        List<Field> entityFields = ClassUtil.getAllFields(entityClass);
167        for (Field field : entityFields) {
168
169            Column column = field.getAnnotation(Column.class);
170            if (column != null && column.ignore()) {
171                continue; // ignore
172            }
173
174
175            if (Modifier.isStatic(field.getModifiers())) {
176                //ignore static field
177                continue;
178            }
179
180
181            //未配置 typeHandler 的情况下,只支持基本数据类型,不支持比如 list set 或者自定义的类等
182            if ((column == null || column.typeHandler() == UnknownTypeHandler.class)
183                    && !field.getType().isEnum()
184                    && !defaultSupportColumnTypes.contains(field.getType())) {
185                continue;
186            }
187
188
189            //列名
190            String columnName = column != null && StringUtil.isNotBlank(column.value())
191                    ? column.value()
192                    : (tableInfo.isCamelToUnderline() ? StringUtil.camelToUnderline(field.getName()) : field.getName());
193
194            //逻辑删除字段
195            if (column != null && column.isLogicDelete()) {
196                if (logicDeleteColumn == null) {
197                    logicDeleteColumn = columnName;
198                } else {
199                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
200                }
201            }
202
203            //乐观锁版本字段
204            if (column != null && column.version()) {
205                if (versionColumn == null) {
206                    versionColumn = columnName;
207                } else {
208                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
209                }
210            }
211
212            //租户ID 字段
213            if (column != null && column.tenantId()) {
214                if (tenantIdColumn == null) {
215                    tenantIdColumn = columnName;
216                } else {
217                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
218                }
219            }
220
221            if (column != null && StringUtil.isNotBlank(column.onInsertValue())) {
222                onInsertColumns.put(columnName, column.onInsertValue().trim());
223            }
224
225
226            if (column != null && StringUtil.isNotBlank(column.onUpdateValue())) {
227                onUpdateColumns.put(columnName, column.onUpdateValue().trim());
228            }
229
230
231            if (column != null && column.isLarge()) {
232                largeColumns.add(columnName);
233            }
234
235            if (column == null || (!column.isLarge() && !column.isLogicDelete())) {
236                defaultColumns.add(columnName);
237            }
238
239            Id id = field.getAnnotation(Id.class);
240            ColumnInfo columnInfo;
241            if (id != null) {
242                columnInfo = new IdInfo(columnName, field.getName(), field.getType(), id);
243                idInfos.add((IdInfo) columnInfo);
244            } else {
245                columnInfo = new ColumnInfo();
246                columnInfoList.add(columnInfo);
247            }
248
249            columnInfo.setColumn(columnName);
250            columnInfo.setProperty(field.getName());
251            columnInfo.setPropertyType(field.getType());
252
253            if (column != null && column.typeHandler() != UnknownTypeHandler.class) {
254                Class<?> typeHandlerClass = column.typeHandler();
255                Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration();
256                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
257                TypeHandler<?> typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass);
258                columnInfo.setTypeHandler(typeHandler);
259            }
260
261            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
262            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
263                if (String.class != field.getType()) {
264                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
265                }
266                columnInfo.setMaskType(columnMask.value().trim());
267            }
268
269            if (column != null && column.jdbcType() != JdbcType.UNDEFINED) {
270                columnInfo.setJdbcType(column.jdbcType());
271            }
272
273            if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(field.getName())) {
274                idField = field;
275            }
276        }
277
278
279        if (idInfos.isEmpty() && idField != null) {
280            int index = -1;
281            for (int i = 0; i < columnInfoList.size(); i++) {
282                ColumnInfo columnInfo = columnInfoList.get(i);
283                if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(columnInfo.getProperty())) {
284                    index = i;
285                    break;
286                }
287            }
288            if (index >= 0) {
289                ColumnInfo removedColumnInfo = columnInfoList.remove(index);
290                idInfos.add(new IdInfo(removedColumnInfo));
291            }
292        }
293
294        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
295        tableInfo.setVersionColumn(versionColumn);
296        tableInfo.setTenantIdColumn(tenantIdColumn);
297
298        if (!onInsertColumns.isEmpty()) {
299            tableInfo.setOnInsertColumns(onInsertColumns);
300        }
301
302        if (!onUpdateColumns.isEmpty()) {
303            tableInfo.setOnUpdateColumns(onUpdateColumns);
304        }
305
306        if (!largeColumns.isEmpty()) {
307            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
308        }
309        if (!defaultColumns.isEmpty()) {
310            tableInfo.setDefaultColumns(defaultColumns.toArray(new String[0]));
311        }
312
313        tableInfo.setColumnInfoList(columnInfoList);
314        tableInfo.setPrimaryKeyList(idInfos);
315
316
317        return tableInfo;
318    }
319}