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}