diff --git a/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java b/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java index 4f272f5..a097384 100644 --- a/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java +++ b/src/main/java/com/github/yulichang/interceptor/MPJInterceptor.java @@ -2,10 +2,13 @@ package com.github.yulichang.interceptor; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.github.yulichang.metadata.TableInfo; +import com.github.yulichang.metadata.TableInfoHelper; 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; @@ -15,10 +18,7 @@ import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -39,6 +39,11 @@ public class MPJInterceptor implements Interceptor { */ private static final Map MS_CACHE = new ConcurrentHashMap<>(); + /** + * 缓存ResultMap + */ + private static final Map, ResultMap> RM_CACHE = new ConcurrentHashMap<>(); + @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); @@ -89,10 +94,44 @@ public class MPJInterceptor implements Interceptor { builder.keyProperty(String.join(StringPool.COMMA, ms.getKeyProperties())); } List resultMaps = new ArrayList<>(); - resultMaps.add(new ResultMap.Builder(ms.getConfiguration(), ms.getId(), resultType, EMPTY_RESULT_MAPPING).build()); + resultMaps.add(newResultMap(ms, resultType)); builder.resultMaps(resultMaps); MappedStatement mappedStatement = builder.build(); MS_CACHE.put(id, mappedStatement); return mappedStatement; } + + /** + * 构建resultMap + */ + private ResultMap newResultMap(MappedStatement ms, Class resultType) { + com.baomidou.mybatisplus.core.metadata.TableInfo tableInfo = com.baomidou.mybatisplus.core.metadata.TableInfoHelper.getTableInfo(resultType); + if (tableInfo != null && tableInfo.isAutoInitResultMap()) { + ResultMap resultMap = RM_CACHE.get(resultType); + if (resultMap == null) { + TableInfo info = TableInfoHelper.initTableInfo(ms.getConfiguration(), ms.getId().substring(0, ms.getId().lastIndexOf(".")), resultType); + resultMap = initResultMapIfNeed(info, resultType); + RM_CACHE.put(resultType, resultMap); + } + return resultMap; + } + 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(i.getResultMapping(info.getConfiguration()))); + } + return new ResultMap.Builder(info.getConfiguration(), id, resultType, resultMappings).build(); + } } diff --git a/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java b/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java new file mode 100644 index 0000000..414925f --- /dev/null +++ b/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java @@ -0,0 +1,346 @@ +package com.github.yulichang.metadata; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import lombok.*; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.reflection.Reflector; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.*; + +import java.lang.reflect.Field; +import java.util.Map; + +/** + * 为自定义resultType提供resultMap + *

+ * copy {@link com.baomidou.mybatisplus.core.metadata.TableFieldInfo} + */ +@Getter +@ToString +@EqualsAndHashCode +@SuppressWarnings("serial") +public class TableFieldInfo implements Constants { + + /** + * 属性 + * + * @since 3.3.1 + */ + private final Field field; + /** + * 字段名 + */ + private final String column; + /** + * 属性名 + */ + private final String property; + /** + * 属性表达式#{property}, 可以指定jdbcType, typeHandler等 + */ + private final String el; + /** + * 属性类型 + */ + private final Class propertyType; + /** + * 是否是基本数据类型 + * + * @since 3.4.0 @2020-6-19 + */ + private final boolean isPrimitive; + /** + * 属性是否是 CharSequence 类型 + */ + private final boolean isCharSequence; + /** + * 字段验证策略之 insert + * Refer to {@link TableField#insertStrategy()} + * + * @since added v_3.1.2 @2019-5-7 + */ + private final FieldStrategy insertStrategy; + /** + * 字段验证策略之 update + * Refer to {@link TableField#updateStrategy()} + * + * @since added v_3.1.2 @2019-5-7 + */ + private final FieldStrategy updateStrategy; + /** + * 字段验证策略之 where + * Refer to {@link TableField#whereStrategy()} + * + * @since added v_3.1.2 @2019-5-7 + */ + private final FieldStrategy whereStrategy; + /** + * 是否是乐观锁字段 + */ + private final boolean version; + /** + * 是否进行 select 查询 + *

大字段可设置为 false 不加入 select 查询范围

+ */ + private boolean select = true; + /** + * 是否是逻辑删除字段 + */ + private boolean logicDelete = false; + /** + * 逻辑删除值 + */ + private String logicDeleteValue; + /** + * 逻辑未删除值 + */ + private String logicNotDeleteValue; + /** + * 字段 update set 部分注入 + */ + private String update; + /** + * where 字段比较条件 + */ + private String condition = SqlCondition.EQUAL; + /** + * 字段填充策略 + */ + private FieldFill fieldFill = FieldFill.DEFAULT; + /** + * 表字段是否启用了插入填充 + * + * @since 3.3.0 + */ + private boolean withInsertFill; + /** + * 表字段是否启用了更新填充 + * + * @since 3.3.0 + */ + private boolean withUpdateFill; + /** + * 缓存 sql select + */ + @Setter(AccessLevel.NONE) + private String sqlSelect; + /** + * JDBC类型 + * + * @since 3.1.2 + */ + private JdbcType jdbcType; + /** + * 类型处理器 + * + * @since 3.1.2 + */ + private Class> typeHandler; + + /** + * 全新的 存在 TableField 注解时使用的构造函数 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, TableField tableField, + Reflector reflector, boolean existTableLogic) { + field.setAccessible(true); + this.field = field; + this.version = field.getAnnotation(Version.class) != null; + this.property = field.getName(); + this.propertyType = reflector.getGetterType(this.property); + this.isPrimitive = this.propertyType.isPrimitive(); + this.isCharSequence = StringUtils.isCharSequence(this.propertyType); + this.fieldFill = tableField.fill(); + this.withInsertFill = this.fieldFill == FieldFill.INSERT || this.fieldFill == FieldFill.INSERT_UPDATE; + this.withUpdateFill = this.fieldFill == FieldFill.UPDATE || this.fieldFill == FieldFill.INSERT_UPDATE; + this.update = tableField.update(); + JdbcType jdbcType = tableField.jdbcType(); + final Class typeHandler = tableField.typeHandler(); + final String numericScale = tableField.numericScale(); + String el = this.property; + if (JdbcType.UNDEFINED != jdbcType) { + this.jdbcType = jdbcType; + el += (COMMA + "jdbcType=" + jdbcType.name()); + } + if (UnknownTypeHandler.class != typeHandler) { + this.typeHandler = (Class>) typeHandler; + if (tableField.javaType()) { + String javaType = null; + TypeAliasRegistry registry = tableInfo.getConfiguration().getTypeAliasRegistry(); + Map> typeAliases = registry.getTypeAliases(); + for (Map.Entry> entry : typeAliases.entrySet()) { + if (entry.getValue().equals(propertyType)) { + javaType = entry.getKey(); + break; + } + } + if (javaType == null) { + javaType = propertyType.getName(); + registry.registerAlias(javaType, propertyType); + } + el += (COMMA + "javaType=" + javaType); + } + el += (COMMA + "typeHandler=" + typeHandler.getName()); + } + if (StringUtils.isNotBlank(numericScale)) { + el += (COMMA + "numericScale=" + numericScale); + } + this.el = el; + this.initLogicDelete(dbConfig, field, existTableLogic); + + String column = tableField.value(); + if (StringUtils.isBlank(column)) { + column = this.property; + if (tableInfo.isUnderCamel()) { + /* 开启字段下划线申明 */ + column = StringUtils.camelToUnderline(column); + } + if (dbConfig.isCapitalMode()) { + /* 开启字段全大写申明 */ + column = column.toUpperCase(); + } + } + String columnFormat = dbConfig.getColumnFormat(); + if (StringUtils.isNotBlank(columnFormat) && tableField.keepGlobalFormat()) { + column = String.format(columnFormat, column); + } + + this.column = column; + this.sqlSelect = column; + if (tableInfo.getResultMap() == null && !tableInfo.isAutoInitResultMap() && + TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) { + /* 未设置 resultMap 也未开启自动构建 resultMap, 字段规则又不符合 mybatis 的自动封装规则 */ + String propertyFormat = dbConfig.getPropertyFormat(); + String asProperty = this.property; + if (StringUtils.isNotBlank(propertyFormat)) { + asProperty = String.format(propertyFormat, this.property); + } + this.sqlSelect += (AS + asProperty); + } + + this.insertStrategy = this.chooseFieldStrategy(tableField.insertStrategy(), dbConfig.getInsertStrategy()); + this.updateStrategy = this.chooseFieldStrategy(tableField.updateStrategy(), dbConfig.getUpdateStrategy()); + this.whereStrategy = this.chooseFieldStrategy(tableField.whereStrategy(), dbConfig.getSelectStrategy()); + + if (StringUtils.isNotBlank(tableField.condition())) { + // 细粒度条件控制 + this.condition = tableField.condition(); + } + + // 字段是否注入查询 + this.select = tableField.select(); + } + + /** + * 优先使用单个字段注解,否则使用全局配置 + */ + private FieldStrategy chooseFieldStrategy(FieldStrategy fromAnnotation, FieldStrategy fromDbConfig) { + return fromAnnotation == FieldStrategy.DEFAULT ? fromDbConfig : fromAnnotation; + } + + /** + * 不存在 TableField 注解时, 使用的构造函数 + */ + public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, Reflector reflector, + boolean existTableLogic) { + field.setAccessible(true); + this.field = field; + this.version = field.getAnnotation(Version.class) != null; + this.property = field.getName(); + this.propertyType = reflector.getGetterType(this.property); + this.isPrimitive = this.propertyType.isPrimitive(); + this.isCharSequence = StringUtils.isCharSequence(this.propertyType); + this.el = this.property; + this.insertStrategy = dbConfig.getInsertStrategy(); + this.updateStrategy = dbConfig.getUpdateStrategy(); + this.whereStrategy = dbConfig.getSelectStrategy(); + this.initLogicDelete(dbConfig, field, existTableLogic); + + String column = this.property; + if (tableInfo.isUnderCamel()) { + /* 开启字段下划线申明 */ + column = StringUtils.camelToUnderline(column); + } + if (dbConfig.isCapitalMode()) { + /* 开启字段全大写申明 */ + column = column.toUpperCase(); + } + + String columnFormat = dbConfig.getColumnFormat(); + if (StringUtils.isNotBlank(columnFormat)) { + column = String.format(columnFormat, column); + } + + this.column = column; + this.sqlSelect = column; + if (tableInfo.getResultMap() == null && !tableInfo.isAutoInitResultMap() && + TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) { + /* 未设置 resultMap 也未开启自动构建 resultMap, 字段规则又不符合 mybatis 的自动封装规则 */ + String propertyFormat = dbConfig.getPropertyFormat(); + String asProperty = this.property; + if (StringUtils.isNotBlank(propertyFormat)) { + asProperty = String.format(propertyFormat, this.property); + } + this.sqlSelect += (AS + asProperty); + } + } + + /** + * 逻辑删除初始化 + * + * @param dbConfig 数据库全局配置 + * @param field 字段属性对象 + */ + private void initLogicDelete(GlobalConfig.DbConfig dbConfig, Field field, boolean existTableLogic) { + /* 获取注解属性,逻辑处理字段 */ + TableLogic tableLogic = field.getAnnotation(TableLogic.class); + if (null != tableLogic) { + if (StringUtils.isNotBlank(tableLogic.value())) { + this.logicNotDeleteValue = tableLogic.value(); + } else { + this.logicNotDeleteValue = dbConfig.getLogicNotDeleteValue(); + } + if (StringUtils.isNotBlank(tableLogic.delval())) { + this.logicDeleteValue = tableLogic.delval(); + } else { + this.logicDeleteValue = dbConfig.getLogicDeleteValue(); + } + this.logicDelete = true; + } else if (!existTableLogic) { + String deleteField = dbConfig.getLogicDeleteField(); + if (StringUtils.isNotBlank(deleteField) && this.property.equals(deleteField)) { + this.logicNotDeleteValue = dbConfig.getLogicNotDeleteValue(); + this.logicDeleteValue = dbConfig.getLogicDeleteValue(); + this.logicDelete = true; + } + } + } + + /** + * 获取 ResultMapping + * + * @param configuration MybatisConfiguration + * @return ResultMapping + */ + public ResultMapping getResultMapping(final Configuration configuration) { + ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, + StringUtils.getTargetColumn(column), propertyType); + TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry(); + if (jdbcType != null && jdbcType != JdbcType.UNDEFINED) { + builder.jdbcType(jdbcType); + } + if (typeHandler != null && typeHandler != UnknownTypeHandler.class) { + TypeHandler typeHandler = registry.getMappingTypeHandler(this.typeHandler); + if (typeHandler == null) { + typeHandler = registry.getInstance(propertyType, this.typeHandler); + // todo 这会有影响 registry.register(typeHandler); + } + builder.typeHandler(typeHandler); + } + return builder.build(); + } + +} diff --git a/src/main/java/com/github/yulichang/metadata/TableInfo.java b/src/main/java/com/github/yulichang/metadata/TableInfo.java new file mode 100644 index 0000000..1a8ecdf --- /dev/null +++ b/src/main/java/com/github/yulichang/metadata/TableInfo.java @@ -0,0 +1,220 @@ +package com.github.yulichang.metadata; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import lombok.AccessLevel; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.apache.ibatis.session.Configuration; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 为自定义resultType提供resultMap + *

+ * copy {@link com.baomidou.mybatisplus.core.metadata.TableInfo} + */ +@Data +@Setter(AccessLevel.PACKAGE) +@Accessors(chain = true) +@SuppressWarnings("serial") +public class TableInfo implements Constants { + + /** + * 实体类型 + */ + private Class entityType; + /** + * 表主键ID 类型 + */ + private IdType idType = IdType.NONE; + /** + * 表名称 + */ + private String tableName; + /** + * 表映射结果集 + */ + private String resultMap; + /** + * 是否是需要自动生成的 resultMap + */ + private boolean autoInitResultMap; + /** + * 主键是否有存在字段名与属性名关联 + *

true: 表示要进行 as

+ */ + private boolean keyRelated; + /** + * 表主键ID 字段名 + */ + private String keyColumn; + /** + * 表主键ID 属性名 + */ + private String keyProperty; + /** + * 表主键ID 属性类型 + */ + private Class keyType; + /** + * 表主键ID Sequence + */ + private KeySequence keySequence; + /** + * 表字段信息列表 + */ + private List fieldList; + /** + * 命名空间 (对应的 mapper 接口的全类名) + */ + private String currentNamespace; + /** + * MybatisConfiguration 标记 (Configuration内存地址值) + */ + @Getter + private Configuration configuration; + /** + * 是否开启下划线转驼峰 + *

+ * 未注解指定字段名的情况下,用于自动从 property 推算 column 的命名 + */ + private boolean underCamel; + /** + * 缓存包含主键及字段的 sql select + */ + @Setter(AccessLevel.NONE) + @Getter(AccessLevel.NONE) + private String allSqlSelect; + /** + * 缓存主键字段的 sql select + */ + @Setter(AccessLevel.NONE) + @Getter(AccessLevel.NONE) + private String sqlSelect; + /** + * 表字段是否启用了插入填充 + * + * @since 3.3.0 + */ + @Getter + @Setter(AccessLevel.NONE) + private boolean withInsertFill; + /** + * 表字段是否启用了更新填充 + * + * @since 3.3.0 + */ + @Getter + @Setter(AccessLevel.NONE) + private boolean withUpdateFill; + /** + * 表字段是否启用了逻辑删除 + * + * @since 3.4.0 + */ + @Getter + @Setter(AccessLevel.NONE) + private boolean withLogicDelete; + /** + * 逻辑删除字段 + * + * @since 3.4.0 + */ + @Getter + @Setter(AccessLevel.NONE) + private TableFieldInfo logicDeleteFieldInfo; + /** + * 表字段是否启用了乐观锁 + * + * @since 3.3.1 + */ + @Getter + @Setter(AccessLevel.NONE) + private boolean withVersion; + /** + * 乐观锁字段 + * + * @since 3.3.1 + */ + @Getter + @Setter(AccessLevel.NONE) + private TableFieldInfo versionFieldInfo; + + public TableInfo(Class entityType) { + this.entityType = entityType; + } + + /** + * 获得注入的 SQL Statement + * + * @param sqlMethod MybatisPlus 支持 SQL 方法 + * @return SQL Statement + * @deprecated 3.4.0 如果存在的多mapper共用一个实体的情况,这里可能会出现获取命名空间错误的情况 + */ + @Deprecated + public String getSqlStatement(String sqlMethod) { + return currentNamespace + DOT + sqlMethod; + } + + /** + * 设置 Configuration + */ + void setConfiguration(Configuration configuration) { + Assert.notNull(configuration, "Error: You need Initialize MybatisConfiguration !"); + this.configuration = configuration; + this.underCamel = configuration.isMapUnderscoreToCamelCase(); + } + + /** + * 是否有主键 + * + * @return 是否有 + */ + public boolean havePK() { + return StringUtils.isNotBlank(keyColumn); + } + + void setFieldList(List fieldList) { + this.fieldList = fieldList; + AtomicInteger logicDeleted = new AtomicInteger(); + AtomicInteger version = new AtomicInteger(); + fieldList.forEach(i -> { + if (i.isLogicDelete()) { + this.withLogicDelete = true; + this.logicDeleteFieldInfo = i; + logicDeleted.getAndAdd(1); + } + if (i.isWithInsertFill()) { + this.withInsertFill = true; + } + if (i.isWithUpdateFill()) { + this.withUpdateFill = true; + } + if (i.isVersion()) { + this.withVersion = true; + this.versionFieldInfo = i; + version.getAndAdd(1); + } + }); + /* 校验字段合法性 */ + Assert.isTrue(logicDeleted.get() <= 1, "@TableLogic not support more than one in Class: \"%s\"", entityType.getName()); + Assert.isTrue(version.get() <= 1, "@Version not support more than one in Class: \"%s\"", entityType.getName()); + } + + public List getFieldList() { + return Collections.unmodifiableList(fieldList); + } + + @Deprecated + public boolean isLogicDelete() { + return withLogicDelete; + } +} diff --git a/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java b/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java new file mode 100644 index 0000000..1f27e73 --- /dev/null +++ b/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java @@ -0,0 +1,349 @@ +package com.github.yulichang.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; + +/** + * 为自定义resultType提供resultMap + *

+ * copy {@link com.baomidou.mybatisplus.core.metadata.TableInfoHelper} + */ +public class TableInfoHelper { + + private static final Log logger = LogFactory.getLog(TableInfoHelper.class); + + /** + * 默认表主键名称 + */ + private static final String DEFAULT_ID_NAME = "id"; + + 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); + 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 (null != dbConfig.getKeyGenerator()) { + 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); + + 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)); + continue; + } + + /* 无 @TableField 注解的字段初始化 */ + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic)); + } + + /* 字段列表 */ + 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 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/wrapper/MPJJoinLambdaQueryWrapper.java b/src/main/java/com/github/yulichang/wrapper/MPJJoinLambdaQueryWrapper.java index 0646607..70b4236 100644 --- a/src/main/java/com/github/yulichang/wrapper/MPJJoinLambdaQueryWrapper.java +++ b/src/main/java/com/github/yulichang/wrapper/MPJJoinLambdaQueryWrapper.java @@ -8,7 +8,6 @@ package com.github.yulichang.wrapper; * @author yulichang */ @Deprecated -@SuppressWarnings("DeprecatedIsStillUsed") public class MPJJoinLambdaQueryWrapper extends MPJLambdaWrapper {