diff --git a/src/main/java/com/baomidou/mybatisplus/core/metadata/MPJTableInfoHelper.java b/src/main/java/com/baomidou/mybatisplus/core/metadata/MPJTableInfoHelper.java new file mode 100644 index 0000000..c72771b --- /dev/null +++ b/src/main/java/com/baomidou/mybatisplus/core/metadata/MPJTableInfoHelper.java @@ -0,0 +1,387 @@ +package com.baomidou.mybatisplus.core.metadata; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.toolkit.*; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.apache.ibatis.reflection.Reflector; +import org.apache.ibatis.reflection.ReflectorFactory; +import org.apache.ibatis.session.Configuration; + +import java.lang.reflect.Field; +import java.util.*; + +import static java.util.stream.Collectors.toList; + +/** + * 拷贝 {@link TableInfoHelper} + * + *

用于构建resultType(DTO)对应的TableInfo + *

拷贝这个类用于更好的兼容mybatis-plus的全部功能 + *

由于 {@link TableInfo} 权限限制,所以新建 com.baomidou.mybatisplus.core.metadata 这个包 + *

为什么不把 {@link TableInfo} 这个类拷贝出来? 因为无法限制用户使用那个版本, 而TableInfo会随着版本而改动, + * 使用 mybatis-plus 的TableInfo能够兼容所有版本,也能跟好的维护 + * + * @author yulichang + * @see TableInfoHelper + */ +public class MPJTableInfoHelper { + + private static final Log logger = LogFactory.getLog(TableInfoHelper.class); + + /** + * 默认表主键名称 + */ + private static final String DEFAULT_ID_NAME = "id"; + + /** + *

+ * 实体类反射获取表信息【初始化】 + *

+ * + * @param clazz 反射实体类 + * @return 数据库表反射信息 + */ + public synchronized static TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class clazz) { + /* 没有获取到缓存信息,则初始化 */ + TableInfo tableInfo = new TableInfo(clazz); + tableInfo.setCurrentNamespace(currentNamespace); + tableInfo.setConfiguration(configuration); + GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration); + + /* 初始化表名相关 */ + final String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo); + + List excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList(); + + /* 初始化字段相关 */ + initTableFields(clazz, globalConfig, tableInfo, excludePropertyList); + + /* 自动构建 resultMap */ + tableInfo.initResultMapIfNeed(); + + /* 缓存 lambda */ + LambdaUtils.installCache(tableInfo); + return tableInfo; + } + + /** + *

+ * 初始化 表数据库类型,表名,resultMap + *

+ * + * @param clazz 实体类 + * @param globalConfig 全局配置 + * @param tableInfo 数据库表反射信息 + * @return 需要排除的字段名 + */ + private static String[] initTableName(Class clazz, GlobalConfig globalConfig, TableInfo tableInfo) { + /* 数据库全局配置 */ + GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig(); + TableName table = clazz.getAnnotation(TableName.class); + + String tableName = clazz.getSimpleName(); + String tablePrefix = dbConfig.getTablePrefix(); + String schema = dbConfig.getSchema(); + boolean tablePrefixEffect = true; + String[] excludeProperty = null; + + if (table != null) { + if (StringUtils.isNotBlank(table.value())) { + tableName = table.value(); + if (StringUtils.isNotBlank(tablePrefix) && !table.keepGlobalPrefix()) { + tablePrefixEffect = false; + } + } else { + tableName = initTableNameWithDbConfig(tableName, dbConfig); + } + if (StringUtils.isNotBlank(table.schema())) { + schema = table.schema(); + } + /* 表结果集映射 */ + if (StringUtils.isNotBlank(table.resultMap())) { + tableInfo.setResultMap(table.resultMap()); + } + tableInfo.setAutoInitResultMap(table.autoResultMap()); + excludeProperty = table.excludeProperty(); + } else { + tableName = initTableNameWithDbConfig(tableName, dbConfig); + } + + String targetTableName = tableName; + if (StringUtils.isNotBlank(tablePrefix) && tablePrefixEffect) { + targetTableName = tablePrefix + targetTableName; + } + if (StringUtils.isNotBlank(schema)) { + targetTableName = schema + StringPool.DOT + targetTableName; + } + + tableInfo.setTableName(targetTableName); + + /* 开启了自定义 KEY 生成器 */ + if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) { + tableInfo.setKeySequence(clazz.getAnnotation(KeySequence.class)); + } + return excludeProperty; + } + + /** + * 根据 DbConfig 初始化 表名 + * + * @param className 类名 + * @param dbConfig DbConfig + * @return 表名 + */ + private static String initTableNameWithDbConfig(String className, GlobalConfig.DbConfig dbConfig) { + String tableName = className; + // 开启表名下划线申明 + if (dbConfig.isTableUnderline()) { + tableName = StringUtils.camelToUnderline(tableName); + } + // 大写命名判断 + if (dbConfig.isCapitalMode()) { + tableName = tableName.toUpperCase(); + } else { + // 首字母小写 + tableName = StringUtils.firstToLowerCase(tableName); + } + return tableName; + } + + /** + *

+ * 初始化 表主键,表字段 + *

+ * + * @param clazz 实体类 + * @param globalConfig 全局配置 + * @param tableInfo 数据库表反射信息 + */ + private static void initTableFields(Class clazz, GlobalConfig globalConfig, TableInfo tableInfo, List excludeProperty) { + /* 数据库全局配置 */ + GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig(); + ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory(); + Reflector reflector = reflectorFactory.findForClass(clazz); + List list = getAllFields(clazz); + // 标记是否读取到主键 + boolean isReadPK = false; + // 是否存在 @TableId 注解 + boolean existTableId = isExistTableId(list); + // 是否存在 @TableLogic 注解 + boolean existTableLogic = isExistTableLogic(list); + // 是否存在 @OrderBy 注解 + boolean existOrderBy = isExistOrderBy(list); + + List fieldList = new ArrayList<>(list.size()); + for (Field field : list) { + if (excludeProperty.contains(field.getName())) { + continue; + } + + /* 主键ID 初始化 */ + if (existTableId) { + TableId tableId = field.getAnnotation(TableId.class); + if (tableId != null) { + if (isReadPK) { + throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName()); + } else { + initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId, reflector); + isReadPK = true; + continue; + } + } + } else if (!isReadPK) { + isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, reflector); + if (isReadPK) { + continue; + } + } + final TableField tableField = field.getAnnotation(TableField.class); + + /* 有 @TableField 注解的字段初始化 */ + if (tableField != null) { + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic, existOrderBy)); + continue; + } + + /* 无 @TableField 注解的字段初始化 */ + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, existOrderBy)); + } + + /* 字段列表 */ + tableInfo.setFieldList(fieldList); + + /* 未发现主键注解,提示警告信息 */ + if (!isReadPK) { + logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName())); + } + } + + /** + *

+ * 判断主键注解是否存在 + *

+ * + * @param list 字段列表 + * @return true 为存在 @TableId 注解; + */ + public static boolean isExistTableId(List list) { + return list.stream().anyMatch(field -> field.isAnnotationPresent(TableId.class)); + } + + /** + *

+ * 判断逻辑删除注解是否存在 + *

+ * + * @param list 字段列表 + * @return true 为存在 @TableId 注解; + */ + public static boolean isExistTableLogic(List list) { + return list.stream().anyMatch(field -> field.isAnnotationPresent(TableLogic.class)); + } + + /** + *

+ * 判断排序注解是否存在 + *

+ * + * @param list 字段列表 + * @return true 为存在 @TableId 注解; + */ + public static boolean isExistOrderBy(List list) { + return list.stream().anyMatch(field -> field.isAnnotationPresent(OrderBy.class)); + } + + /** + *

+ * 主键属性初始化 + *

+ * + * @param dbConfig 全局配置信息 + * @param tableInfo 表信息 + * @param field 字段 + * @param tableId 注解 + * @param reflector Reflector + */ + private static void initTableIdWithAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, + Field field, TableId tableId, Reflector reflector) { + boolean underCamel = tableInfo.isUnderCamel(); + final String property = field.getName(); + if (field.getAnnotation(TableField.class) != null) { + logger.warn(String.format("This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!", + property, tableInfo.getEntityType().getName())); + } + /* 主键策略( 注解 > 全局 ) */ + // 设置 Sequence 其他策略无效 + if (IdType.NONE == tableId.type()) { + tableInfo.setIdType(dbConfig.getIdType()); + } else { + tableInfo.setIdType(tableId.type()); + } + + /* 字段 */ + String column = property; + if (StringUtils.isNotBlank(tableId.value())) { + column = tableId.value(); + } else { + // 开启字段下划线申明 + if (underCamel) { + column = StringUtils.camelToUnderline(column); + } + // 全局大写命名 + if (dbConfig.isCapitalMode()) { + column = column.toUpperCase(); + } + } + final Class keyType = reflector.getGetterType(property); + if (keyType.isPrimitive()) { + logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", + property, tableInfo.getEntityType().getName())); + } + tableInfo.setKeyRelated(checkRelated(underCamel, property, column)) + .setKeyColumn(column) + .setKeyProperty(property) + .setKeyType(keyType); + } + + /** + *

+ * 主键属性初始化 + *

+ * + * @param tableInfo 表信息 + * @param field 字段 + * @param reflector Reflector + * @return true 继续下一个属性判断,返回 continue; + */ + private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, + Field field, Reflector reflector) { + final String property = field.getName(); + if (DEFAULT_ID_NAME.equalsIgnoreCase(property)) { + if (field.getAnnotation(TableField.class) != null) { + logger.warn(String.format("This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!", + property, tableInfo.getEntityType().getName())); + } + String column = property; + if (dbConfig.isCapitalMode()) { + column = column.toUpperCase(); + } + final Class keyType = reflector.getGetterType(property); + if (keyType.isPrimitive()) { + logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", + property, tableInfo.getEntityType().getName())); + } + tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column)) + .setIdType(dbConfig.getIdType()) + .setKeyColumn(column) + .setKeyProperty(property) + .setKeyType(keyType); + return true; + } + return false; + } + + /** + * 判定 related 的值 + *

+ * 为 true 表示不符合规则 + * + * @param underCamel 驼峰命名 + * @param property 属性名 + * @param column 字段名 + * @return related + */ + public static boolean checkRelated(boolean underCamel, String property, String column) { + column = StringUtils.getTargetColumn(column); + String propertyUpper = property.toUpperCase(Locale.ENGLISH); + String columnUpper = column.toUpperCase(Locale.ENGLISH); + if (underCamel) { + // 开启了驼峰并且 column 包含下划线 + return !(propertyUpper.equals(columnUpper) || + propertyUpper.equals(columnUpper.replace(StringPool.UNDERSCORE, StringPool.EMPTY))); + } else { + // 未开启驼峰,直接判断 property 是否与 column 相同(全大写) + return !propertyUpper.equals(columnUpper); + } + } + + /** + *

+ * 获取该类的所有属性列表 + *

+ * + * @param clazz 反射类 + * @return 属性集合 + */ + public static List getAllFields(Class clazz) { + List fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz)); + return fieldList.stream() + .filter(field -> { + /* 过滤注解非表字段属性 */ + TableField tableField = field.getAnnotation(TableField.class); + return (tableField == null || tableField.exist()); + }).collect(toList()); + } +} diff --git a/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java b/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java index 0fa4cdc..fc983d9 100644 --- a/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java +++ b/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java @@ -1,16 +1,15 @@ package com.github.yulichang.interceptor; -import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.MPJTableInfoHelper; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.StringPool; -import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.github.yulichang.exception.MPJException; import com.github.yulichang.method.MPJResultType; import com.github.yulichang.toolkit.Constant; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.ResultFlag; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.plugin.Interceptor; @@ -20,12 +19,11 @@ import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; -import org.apache.ibatis.type.TypeHandlerRegistry; -import org.apache.ibatis.type.UnknownTypeHandler; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** @@ -44,7 +42,7 @@ public class MPJInterceptor implements Interceptor { /** * 缓存MappedStatement,不需要每次都去重写构建MappedStatement */ - private static final Map MS_CACHE = new ConcurrentHashMap<>(); + private static final Map> MS_CACHE = new ConcurrentHashMap<>(); @Override public Object intercept(Invocation invocation) throws Throwable { @@ -77,9 +75,12 @@ public class MPJInterceptor implements Interceptor { */ public MappedStatement newMappedStatement(MappedStatement ms, Class resultType) { String id = ms.getId() + StringPool.UNDERSCORE + resultType.getName(); - MappedStatement statement = MS_CACHE.get(id); - if (Objects.nonNull(statement)) { - return statement; + Map statementMap = MS_CACHE.get(id); + if (CollectionUtils.isNotEmpty(statementMap)) { + MappedStatement statement = statementMap.get(ms.getConfiguration()); + if (Objects.nonNull(statement)) { + return statement; + } } MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), id, ms.getSqlSource(), ms.getSqlCommandType()) .resource(ms.getResource()) @@ -99,7 +100,12 @@ public class MPJInterceptor implements Interceptor { resultMaps.add(newResultMap(ms, resultType)); builder.resultMaps(resultMaps); MappedStatement mappedStatement = builder.build(); - MS_CACHE.put(id, mappedStatement); + + if (statementMap == null) { + statementMap = new ConcurrentHashMap<>(); + MS_CACHE.put(id, statementMap); + } + statementMap.put(ms.getConfiguration(), mappedStatement); return mappedStatement; } @@ -109,50 +115,13 @@ public class MPJInterceptor implements Interceptor { private ResultMap newResultMap(MappedStatement ms, Class resultType) { TableInfo tableInfo = TableInfoHelper.getTableInfo(resultType); if (tableInfo != null && tableInfo.isAutoInitResultMap() && tableInfo.getEntityType() == resultType) { - return initResultMapIfNeed(tableInfo, resultType); + return ms.getConfiguration().getResultMap(tableInfo.getResultMap()); + } + TableInfo infoDTO = MPJTableInfoHelper.initTableInfo(ms.getConfiguration(), + ms.getId().substring(0, ms.getId().lastIndexOf(".")), resultType); + if (infoDTO.isAutoInitResultMap()) { + return ms.getConfiguration().getResultMap(infoDTO.getResultMap()); } return new ResultMap.Builder(ms.getConfiguration(), ms.getId(), resultType, EMPTY_RESULT_MAPPING).build(); } - - /** - * 构建resultMap - */ - private ResultMap initResultMapIfNeed(TableInfo info, Class resultType) { - String id = info.getCurrentNamespace() + ".mybatis-plus-join_" + resultType.getSimpleName(); - List resultMappings = new ArrayList<>(); - if (info.havePK()) { - ResultMapping idMapping = new ResultMapping.Builder(info.getConfiguration(), info.getKeyProperty(), info.getKeyColumn(), info.getKeyType()) - .flags(Collections.singletonList(ResultFlag.ID)).build(); - resultMappings.add(idMapping); - } - if (CollectionUtils.isNotEmpty(info.getFieldList())) { - info.getFieldList().forEach(i -> resultMappings.add(getResultMapping(i, info.getConfiguration()))); - } - return new ResultMap.Builder(info.getConfiguration(), id, resultType, resultMappings).build(); - } - - - /** - * 获取 ResultMapping - * - * @param configuration MybatisConfiguration - * @return ResultMapping - */ - private ResultMapping getResultMapping(TableFieldInfo info, final Configuration configuration) { - ResultMapping.Builder builder = new ResultMapping.Builder(configuration, info.getProperty(), - StringUtils.getTargetColumn(info.getColumn()), info.getPropertyType()); - TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry(); - if (info.getJdbcType() != null && info.getJdbcType() != JdbcType.UNDEFINED) { - builder.jdbcType(info.getJdbcType()); - } - if (info.getTypeHandler() != null && info.getTypeHandler() != UnknownTypeHandler.class) { - TypeHandler typeHandler = registry.getMappingTypeHandler(info.getTypeHandler()); - if (typeHandler == null) { - typeHandler = registry.getInstance(info.getPropertyType(), info.getTypeHandler()); - // todo 这会有影响 registry.register(typeHandler); - } - builder.typeHandler(typeHandler); - } - return builder.build(); - } } diff --git a/src/main/java/com/github/yulichang/toolkit/Constant.java b/src/main/java/com/github/yulichang/toolkit/Constant.java index 352be8a..31854d7 100644 --- a/src/main/java/com/github/yulichang/toolkit/Constant.java +++ b/src/main/java/com/github/yulichang/toolkit/Constant.java @@ -15,8 +15,6 @@ public interface Constant { String ON = " ON "; - String EQUALS = " = "; - String JOIN = "JOIN"; String LEFT = "LEFT"; @@ -46,14 +44,4 @@ public interface Constant { * " t" */ String SPACE_TABLE_ALIAS = StringPool.SPACE + Constant.TABLE_ALIAS; - - /** - * " ON t" - */ - String ON_TABLE_ALIAS = Constant.ON + Constant.TABLE_ALIAS; - - /** - * " = t" - */ - String EQUALS_TABLE_ALIAS = Constant.EQUALS + Constant.TABLE_ALIAS; }