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
164        List<Field> entityFields = ClassUtil.getAllFields(entityClass);
165        for (Field field : entityFields) {
166
167            Column column = field.getAnnotation(Column.class);
168            if (column != null && column.ignore()) {
169                continue; // ignore
170            }
171
172
173            if (Modifier.isStatic(field.getModifiers())) {
174                //ignore static field
175                continue;
176            }
177
178
179            //未配置 typeHandler 的情况下,只支持基本数据类型,不支持比如 list set 或者自定义的类等
180            if ((column == null || column.typeHandler() == UnknownTypeHandler.class)
181                    && !field.getType().isEnum()
182                    && !defaultSupportColumnTypes.contains(field.getType())) {
183                continue;
184            }
185
186
187            //列名
188            String columnName = column != null && StringUtil.isNotBlank(column.value())
189                    ? column.value()
190                    : (tableInfo.isCamelToUnderline() ? StringUtil.camelToUnderline(field.getName()) : field.getName());
191
192            //逻辑删除字段
193            if (column != null && column.isLogicDelete()) {
194                if (logicDeleteColumn == null) {
195                    logicDeleteColumn = columnName;
196                } else {
197                    throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName());
198                }
199            }
200
201            //乐观锁版本字段
202            if (column != null && column.version()) {
203                if (versionColumn == null) {
204                    versionColumn = columnName;
205                } else {
206                    throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName());
207                }
208            }
209
210            //租户ID 字段
211            if (column != null && column.tenantId()) {
212                if (tenantIdColumn == null) {
213                    tenantIdColumn = columnName;
214                } else {
215                    throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName());
216                }
217            }
218
219            if (column != null && StringUtil.isNotBlank(column.onInsertValue())) {
220                onInsertColumns.put(columnName, column.onInsertValue().trim());
221            }
222
223
224            if (column != null && StringUtil.isNotBlank(column.onUpdateValue())) {
225                onUpdateColumns.put(columnName, column.onUpdateValue().trim());
226            }
227
228
229            if (column != null && column.isLarge()) {
230                largeColumns.add(columnName);
231            }
232
233
234            Id id = field.getAnnotation(Id.class);
235            ColumnInfo columnInfo;
236            if (id != null) {
237                columnInfo = new IdInfo(columnName, field.getName(), field.getType(), id);
238                idInfos.add((IdInfo) columnInfo);
239            } else {
240                columnInfo = new ColumnInfo();
241                columnInfoList.add(columnInfo);
242            }
243
244            columnInfo.setColumn(columnName);
245            columnInfo.setProperty(field.getName());
246            columnInfo.setPropertyType(field.getType());
247
248            if (column != null && column.typeHandler() != UnknownTypeHandler.class) {
249                Class<?> typeHandlerClass = column.typeHandler();
250                Configuration configuration = FlexGlobalConfig.getDefaultConfig().getConfiguration();
251                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
252                TypeHandler<?> typeHandler = typeHandlerRegistry.getInstance(columnInfo.getPropertyType(), typeHandlerClass);
253                columnInfo.setTypeHandler(typeHandler);
254            }
255
256            ColumnMask columnMask = field.getAnnotation(ColumnMask.class);
257            if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) {
258                if (String.class != field.getType()) {
259                    throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName());
260                }
261                columnInfo.setMaskType(columnMask.value().trim());
262            }
263
264            if (column != null && column.jdbcType() != JdbcType.UNDEFINED) {
265                columnInfo.setJdbcType(column.jdbcType());
266            }
267
268            if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(field.getName())) {
269                idField = field;
270            }
271        }
272
273
274        if (idInfos.isEmpty() && idField != null) {
275            int index = -1;
276            for (int i = 0; i < columnInfoList.size(); i++) {
277                ColumnInfo columnInfo = columnInfoList.get(i);
278                if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(columnInfo.getProperty())) {
279                    index = i;
280                    break;
281                }
282            }
283            if (index >= 0) {
284                ColumnInfo removedColumnInfo = columnInfoList.remove(index);
285                idInfos.add(new IdInfo(removedColumnInfo));
286            }
287        }
288
289        tableInfo.setLogicDeleteColumn(logicDeleteColumn);
290        tableInfo.setVersionColumn(versionColumn);
291        tableInfo.setTenantIdColumn(tenantIdColumn);
292
293        if (!onInsertColumns.isEmpty()) {
294            tableInfo.setOnInsertColumns(onInsertColumns);
295        }
296
297        if (!onUpdateColumns.isEmpty()) {
298            tableInfo.setOnUpdateColumns(onUpdateColumns);
299        }
300
301        if (!largeColumns.isEmpty()) {
302            tableInfo.setLargeColumns(largeColumns.toArray(new String[0]));
303        }
304
305        tableInfo.setColumnInfoList(columnInfoList);
306        tableInfo.setPrimaryKeyList(idInfos);
307
308
309        return tableInfo;
310    }
311}