diff --git a/pom.xml b/pom.xml index 765a8f3..6bbb3c3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ com.imyeyu.spring timi-spring - 0.0.1 + 0.0.2 jar diff --git a/src/main/java/com/imyeyu/spring/mapper/DynamicTableMapper.java b/src/main/java/com/imyeyu/spring/mapper/DynamicTableMapper.java new file mode 100644 index 0000000..cb2300d --- /dev/null +++ b/src/main/java/com/imyeyu/spring/mapper/DynamicTableMapper.java @@ -0,0 +1,153 @@ +package com.imyeyu.spring.mapper; + +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.bean.PageResult; +import com.imyeyu.spring.util.DynamicTableSQLProvider; +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; + +import java.util.List; + +/** + * 支持动态表名的 SQL 映射接口 + *

相比 {@link BaseMapper},所有方法都需要显式传入表名参数

+ * + * @param 实体类型 + * @param

主键类型 + * @author 夜雨 + * @since 2026-01-07 11:00 + */ +public interface DynamicTableMapper { + + /** + * 根据 Page 对象查询数据列表 + * + * @param tableName 表名 + * @param page 分页参数 + * @return 数据列表 + */ + @SelectProvider(type = DynamicTableSQLProvider.class, method = "selectByPage") + List selectByPage(@Param("tableName") String tableName, @Param("page") Page page); + + /** + * 根据 Page 对象统计数据量 + * + * @param tableName 表名 + * @param page 分页参数 + * @return 数据量 + */ + @SelectProvider(type = DynamicTableSQLProvider.class, method = "countByPage") + long countByPage(@Param("tableName") String tableName, @Param("page") Page page); + + /** + * 分页查询 + * + * @param tableName 表名 + * @param page 分页参数 + * @return 分页结果 + */ + default PageResult selectPageResult(String tableName, Page page) { + PageResult result = new PageResult<>(); + result.setTotal(countByPage(tableName, page)); + result.setList(selectByPage(tableName, page)); + return result; + } + + /** + * 查询全部数据 + * + * @param tableName 表名 + * @return 数据列表 + */ + @SelectProvider(type = DynamicTableSQLProvider.class, method = "selectAll") + List selectAll(@Param("tableName") String tableName); + + /** + * 创建数据 + * + * @param tableName 表名 + * @param entity 数据对象 + */ + @InsertProvider(type = DynamicTableSQLProvider.class, method = "insert") + @Options(useGeneratedKeys = true, keyProperty = "entity.id") + void insert(@Param("tableName") String tableName, @Param("entity") T entity); + + /** + * 根据 ID 获取对象 + * + * @param tableName 表名 + * @param id 索引 + * @return 数据对象 + */ + @SelectProvider(type = DynamicTableSQLProvider.class, method = "select") + T select(@Param("tableName") String tableName, @Param("id") P id); + + /** + * 根据示例查询单条数据 + * + * @param tableName 表名 + * @param entity 示例对象 + * @return 数据对象 + */ + @SelectProvider(type = DynamicTableSQLProvider.class, method = "selectByExample") + T selectByExample(@Param("tableName") String tableName, @Param("entity") T entity); + + /** + * 根据示例查询全部数据 + * + * @param tableName 表名 + * @param entity 示例对象 + * @return 数据列表 + */ + @SelectProvider(type = DynamicTableSQLProvider.class, method = "selectAllByExample") + List selectAllByExample(@Param("tableName") String tableName, @Param("entity") T entity); + + /** + * 修改数据 + * + * @param tableName 表名 + * @param entity 数据对象 + */ + @UpdateProvider(type = DynamicTableSQLProvider.class, method = "update") + void update(@Param("tableName") String tableName, @Param("entity") T entity); + + /** + * 选择性更新 + * + * @param tableName 表名 + * @param entity 数据对象 + */ + @UpdateProvider(type = DynamicTableSQLProvider.class, method = "updateSelective") + void updateSelective(@Param("tableName") String tableName, @Param("entity") T entity); + + /** + * 软删除 + * + * @param tableName 表名 + * @param id 索引 + */ + @UpdateProvider(type = DynamicTableSQLProvider.class, method = "delete") + void delete(@Param("tableName") String tableName, @Param("id") P id); + + /** + * 根据示例批量逻辑删除 + * + * @param tableName 表名 + * @param entity 示例对象 + */ + @UpdateProvider(type = DynamicTableSQLProvider.class, method = "deleteAllByExample") + void deleteAllByExample(@Param("tableName") String tableName, @Param("entity") T entity); + + /** + * 销毁(物理删除) + * + * @param tableName 表名 + * @param id 索引 + */ + @DeleteProvider(type = DynamicTableSQLProvider.class, method = "destroy") + void destroy(@Param("tableName") String tableName, @Param("id") P id); +} diff --git a/src/main/java/com/imyeyu/spring/util/BaseSQLProvider.java b/src/main/java/com/imyeyu/spring/util/BaseSQLProvider.java new file mode 100644 index 0000000..2a51018 --- /dev/null +++ b/src/main/java/com/imyeyu/spring/util/BaseSQLProvider.java @@ -0,0 +1,788 @@ +package com.imyeyu.spring.util; + +import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.CallbackArgReturn; +import com.imyeyu.java.bean.timi.TimiCode; +import com.imyeyu.java.bean.timi.TimiException; +import com.imyeyu.java.ref.Ref; +import com.imyeyu.spring.annotation.table.AutoUUID; +import com.imyeyu.spring.annotation.table.Column; +import com.imyeyu.spring.annotation.table.DeleteColumn; +import com.imyeyu.spring.annotation.table.Id; +import com.imyeyu.spring.annotation.table.PageIgnore; +import com.imyeyu.spring.annotation.table.Table; +import com.imyeyu.spring.annotation.table.Transient; +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.entity.Creatable; +import com.imyeyu.spring.entity.Deletable; +import com.imyeyu.spring.entity.Destroyable; +import com.imyeyu.spring.entity.Updatable; +import com.imyeyu.spring.mapper.BaseMapper; +import com.imyeyu.utils.Text; +import com.imyeyu.utils.Time; +import org.apache.ibatis.builder.annotation.ProviderContext; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * SQL 提供器基类 + *

包含所有 SQL 构建逻辑和实体元数据管理

+ * + * @author 夜雨 + * @since 2026-01-07 11:20 + */ +public abstract class BaseSQLProvider { + + /** 反射缓存 */ + private static final Map, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>(); + + /** + * 根据代理器上下文获取 Mapper 实体类元数据 + * + * @param context 代理器上下文 + * @return 实体类元数据 + */ + protected EntityMeta getEntityMeta(ProviderContext context) { + Type[] types = context.getMapperType().getGenericInterfaces(); + ParameterizedType type = (ParameterizedType) types[0]; + Class entityClass = (Class) type.getActualTypeArguments()[0]; + return getEntityMeta(entityClass); + } + + /** + * 获取实体类元数据 + * + * @param entityClass 实体类 + * @return 元数据 + */ + protected EntityMeta getEntityMeta(Class entityClass) { + return ENTITY_META_CACHE.computeIfAbsent(entityClass, EntityMeta::new); + } + + /** + * 构建分页查询 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param page 分页参数 + * @param offset 偏移量占位符 + * @param limit 限制数量占位符 + * @return SQL + */ + protected String buildSelectByPageSQL(EntityMeta meta, String tableName, Page page, String offset, String limit) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectPageClause, tableName)); + + // 添加软删除条件 + appendSoftDeleteCondition(sql, meta); + + // 添加 Page 查询条件 + appendPageConditions(sql, page); + + // 添加排序 + appendOrderBy(sql, meta, page); + + // 添加分页限制 + sql.append(" LIMIT %s, %s".formatted(offset, limit)); + return sql.toString(); + } + + /** + * 构建分页统计 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param page 分页参数 + * @return SQL + */ + protected String buildCountByPageSQL(EntityMeta meta, String tableName, Page page) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT COUNT(*) FROM `%s` WHERE 1 = 1".formatted(tableName)); + + // 添加软删除条件 + appendSoftDeleteCondition(sql, meta); + + // 添加 Page 查询条件 + appendPageConditions(sql, page); + + return sql.toString(); + } + + /** + * 添加软删除条件 + * + * @param sql SQL 构建器 + * @param meta 实体元数据 + */ + protected void appendSoftDeleteCondition(StringBuilder sql, EntityMeta meta) { + if (meta.canDelete) { + sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); + } + } + + /** + * 添加 Page 查询条件(精准查询和模糊查询) + * + * @param sql SQL 构建器 + * @param page 分页参数 + */ + protected void appendPageConditions(StringBuilder sql, Page page) { + // 精准查询 + if (TimiJava.isNotEmpty(page.getEqualsExample())) { + Object obj = page.getEqualsExample(); + EntityMeta metaExample = getEntityMeta(obj.getClass()); + String conditionClause = metaExample.fieldColumnList.stream() + .filter(fc -> fc.isNotEmpty(obj)) + .map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj))) + .collect(Collectors.joining(" AND ")); + if (TimiJava.isNotEmpty(conditionClause)) { + sql.append(" AND ").append(conditionClause); + } + } + + // 模糊查询 + if (TimiJava.isNotEmpty(page.getLikesExample())) { + Object obj = page.getLikesExample(); + EntityMeta metaExample = getEntityMeta(obj.getClass()); + String conditionClause = metaExample.fieldColumnList.stream() + .filter(fc -> fc.isNotEmpty(obj)) + .map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj))) + .collect(Collectors.joining(" OR ")); + if (TimiJava.isNotEmpty(conditionClause)) { + sql.append(" AND (").append(conditionClause).append(')'); + } + } + } + + /** + * 添加排序子句 + * + * @param sql SQL 构建器 + * @param meta 实体元数据 + * @param page 分页参数 + */ + protected void appendOrderBy(StringBuilder sql, EntityMeta meta, Page page) { + if (TimiJava.isNotEmpty(page.getOrderMap())) { + sql.append(" ORDER BY "); + for (Map.Entry item : page.getOrderMap().entrySet()) { + sql.append("`%s` %s, ".formatted( + Text.camelCase2underscore(item.getKey()), + item.getValue().toString() + )); + } + sql.deleteCharAt(sql.length() - 2); + } else { + // 默认排序 + if (meta.canCreate && !meta.canUpdate) { + sql.append(" ORDER BY `created_at` DESC"); + } + if (meta.canCreate && meta.canUpdate) { + sql.append(" ORDER BY COALESCE(`updated_at`, `created_at`) DESC"); + } + } + } + + /** + * 构建示例查询条件子句 + * + * @param meta 实体元数据 + * @param entity 示例实体 + * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 + * @return 条件子句 + */ + protected String buildExampleConditions(EntityMeta meta, Object entity, String paramPrefix) { + return meta.fieldColumnList.stream() + .filter(fc -> fc.isNotEmpty(entity)) + .map(fc -> "`%s` = #{%s%s}".formatted(fc.columnName, paramPrefix, fc.fieldName)) + .collect(Collectors.joining(" AND ")); + } + + /** + * 构建插入 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param entity 实体对象 + * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 + * @return SQL + */ + protected String buildInsertSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix) { + String columns = meta.fieldColumnList.stream() + .map(fc -> "`%s`".formatted(fc.columnName)) + .collect(Collectors.joining(", ")); + String values = meta.fieldColumnList.stream().map(fc -> { + try { + if (fc.isAutoUUID && TimiJava.isEmpty(Ref.getFieldValue(entity, fc.field, String.class))) { + String uuid = UUID.randomUUID().toString(); + if (fc.isAutoUpperUUID) { + uuid = uuid.toUpperCase(); + } + Ref.setFieldValue(entity, fc.field, uuid); + } + } catch (IllegalAccessException e) { + throw new TimiException(TimiCode.ERROR).msgKey("auto set field:%s value error".formatted(fc.fieldName)); + } + if (entity instanceof Creatable creatableEntity && creatableEntity.getCreatedAt() == null) { + creatableEntity.setCreatedAt(Time.now()); + } + return "#{%s%s}".formatted(paramPrefix, fc.fieldName); + }).collect(Collectors.joining(", ")); + return "INSERT INTO `%s` (%s) VALUES (%s)".formatted(tableName, columns, values); + } + + /** + * 构建根据 ID 查询 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param idParam ID 参数占位符(如 "id") + * @return SQL + */ + protected String buildSelectByIdSQL(EntityMeta meta, String tableName, String idParam) { + TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT %s FROM `%s` WHERE `%s` = #{%s}".formatted( + meta.selectAllClause, + tableName, + meta.idFieldColumn.columnName, + idParam + )); + if (meta.canDelete) { + sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); + } + return sql.append(" LIMIT 1").toString(); + } + + /** + * 构建根据示例查询全部 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param entity 示例实体 + * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 + * @return SQL + */ + protected String buildSelectAllByExampleSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix) { + String conditionClause = buildExampleConditions(meta, entity, paramPrefix); + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT %s FROM `%s` WHERE %s".formatted(meta.selectAllClause, tableName, conditionClause)); + if (meta.canDelete) { + if (TimiJava.isNotEmpty(conditionClause)) { + sql.append(" AND "); + } + sql.append("(`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); + } + return sql.toString(); + } + + /** + * 构建更新 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param entity 实体对象 + * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 + * @return SQL + */ + protected String buildUpdateSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix) { + TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); + TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass)); + + if (entity instanceof Updatable updatable) { + updatable.setUpdatedAt(Time.now()); + } + String setClause = meta.fieldColumnList.stream() + .filter(FieldColumn::isNotId) + .map(fc -> "`%s` = #{%s%s}".formatted(fc.columnName, paramPrefix, fc.fieldName)) + .collect(Collectors.joining(", ")); + return "UPDATE `%s` SET %s WHERE `%s` = #{%s%s}".formatted( + tableName, + setClause, + meta.idFieldColumn.columnName, + paramPrefix, + meta.idFieldColumn.fieldName + ); + } + + /** + * 构建选择性更新 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param entity 实体对象 + * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 + * @return SQL + */ + protected String buildUpdateSelectiveSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix) { + TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); + TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass)); + + if (entity instanceof Updatable updatable) { + updatable.setUpdatedAt(Time.now()); + } + String setClause = meta.fieldColumnList.stream() + .filter(FieldColumn::isNotId) + .filter(fc -> fc.isNotNull(entity)) + .map(fc -> "`%s` = #{%s%s}".formatted(fc.columnName, paramPrefix, fc.fieldName)) + .collect(Collectors.joining(", ")); + return "UPDATE `%s` SET %s WHERE `%s` = #{%s%s}".formatted( + tableName, + setClause, + meta.idFieldColumn.columnName, + paramPrefix, + meta.idFieldColumn.fieldName + ); + } + + /** + * 构建软删除 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param idParam ID 参数占位符(如 "id") + * @return SQL + */ + protected String buildDeleteSQL(EntityMeta meta, String tableName, String idParam) { + TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); + TimiException.requiredTrue(meta.canDelete, "not allow soft delete for %s".formatted(meta.entityClass)); + + return "UPDATE `%s` SET `deleted_at` = %s WHERE `%s` = #{%s}".formatted( + tableName, + Time.now(), + meta.idFieldColumn.columnName, + idParam + ); + } + + /** + * 构建批量逻辑删除 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param entity 示例实体 + * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 + * @return SQL + */ + protected String buildDeleteAllByExampleSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix) { + TimiException.required(meta.canDelete, "not allow delete for %s".formatted(meta.entityClass)); + + FieldColumn deleteColumn = meta.getFieldColumnList().stream() + .filter(fc -> fc.isDeleteColumn) + .findFirst() + .orElse(null); + TimiException.required(deleteColumn, "unknown delete column, use com.imyeyu.spring.annotation.table.DeleteColumn annotation on field"); + assert deleteColumn != null; + assert deleteColumn.deleteColumnType != null; + + String delClause = meta.fieldColumnList.stream() + .filter(FieldColumn::isNotId) + .filter(fc -> fc.isNotEmpty(entity)) + .map(fc -> "`%s` = #{%s%s}".formatted(fc.columnName, paramPrefix, fc.fieldName)) + .collect(Collectors.joining(" AND ")); + StringBuilder sql = new StringBuilder("UPDATE `%s` SET `%s` = ".formatted(tableName, deleteColumn.getColumnName())); + sql.append("'").append(switch (deleteColumn.deleteColumnType) { + case UNIX -> Time.now(); + case DATE, DATE_TIME -> new Date(); + }).append("'"); + sql.append(" WHERE ").append(delClause); + return sql.toString(); + } + + /** + * 构建硬删除 SQL + * + * @param meta 实体元数据 + * @param tableName 表名 + * @param idParam ID 参数占位符(如 "id") + * @return SQL + */ + protected String buildDestroySQL(EntityMeta meta, String tableName, String idParam) { + TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); + TimiException.requiredTrue(meta.canDestroy, "not allow destroy for %s".formatted(meta.entityClass)); + + return "DELETE FROM `%s` WHERE `%s` = #{%s}".formatted(tableName, meta.idFieldColumn.columnName, idParam); + } + + /** + * 实体元数据 + * + * @author 夜雨 + * @since 2025-02-05 23:47 + */ + protected static class EntityMeta { + + /** 实体类 */ + final Class entityClass; + + /** 表名 */ + final String table; + + /** 查询字段映射 */ + final String selectAllClause; + + /** 页面查询字段映射 */ + final String selectPageClause; + + /** ID 字段 */ + final FieldColumn idFieldColumn; + + /** 只读的列名字段名映射,Map<列名,字段名> */ + final List fieldColumnList; + + /** true 为可创建 */ + final boolean canCreate; + + /** true 为可更新 */ + final boolean canUpdate; + + /** true 为可删除(软删除) */ + final boolean canDelete; + + /** true 为可销毁(硬删除) */ + final boolean canDestroy; + + /** + * 创建实体元数据 + * + * @param entityClass 实体类型 + */ + public EntityMeta(Class entityClass) { + this.entityClass = entityClass; + + // 表名 + while (entityClass.isAnnotationPresent(Transient.class)) { + entityClass = entityClass.getSuperclass(); + } + Table table = entityClass.getAnnotation(Table.class); + if (table == null) { + this.table = Text.camelCase2underscore(entityClass.getSimpleName()); + } else { + this.table = table.value(); + TimiException.required(this.table, String.format("empty table annotation value for %s entity", entityClass.getName())); + } + List allFieldList = Ref.listAllFields(entityClass); + FieldColumn idFieldColumn = null; + List fieldColumnList = new ArrayList<>(); + for (int i = 0; i < allFieldList.size(); i++) { + Field field = allFieldList.get(i); + if (field.isAnnotationPresent(Transient.class)) { + continue; + } + FieldColumn fieldColumn = new FieldColumn(field); + if (fieldColumn.isId) { + TimiException.requiredNull(idFieldColumn, String.format("multi id field for %s entity", entityClass.getName())); + idFieldColumn = fieldColumn; + } + fieldColumnList.add(fieldColumn); + } + this.selectAllClause = buildSelectClause(fieldColumnList, null); + this.selectPageClause = buildSelectClause(fieldColumnList, fc -> !fc.getField().isAnnotationPresent(PageIgnore.class)); + this.idFieldColumn = idFieldColumn; + this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读 + canCreate = Creatable.class.isAssignableFrom(entityClass); + canUpdate = Updatable.class.isAssignableFrom(entityClass); + canDelete = Deletable.class.isAssignableFrom(entityClass); + canDestroy = Destroyable.class.isAssignableFrom(entityClass); + } + + private String buildSelectClause(List fieldColumnList, CallbackArgReturn callback) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < fieldColumnList.size(); i++) { + FieldColumn fieldColumn = fieldColumnList.get(i); + Field field = fieldColumn.getField(); + if (callback != null && !callback.handler(fieldColumn)) { + continue; + } + Column column = field.getAnnotation(Column.class); + if (column == null) { + sb.append('`').append(fieldColumn.columnName).append('`'); + sb.append(','); + } else { + // 自定义映射列名 + sb.append('`').append(column.value()).append('`'); + sb.append(" AS `").append(fieldColumn.fieldName).append('`'); + sb.append(','); + } + } + return sb.substring(0, sb.length() - 1); + } + + /** + * 获取实体类型 + * + * @return 实体类型 + */ + public Class getEntityClass() { + return entityClass; + } + + /** + * 获取表名 + * + * @return 表名 + */ + public String getTable() { + return table; + } + + /** + * 获取查询字段映射 + * + * @return 查询字段映射 + */ + public String getSelectAllClause() { + return selectAllClause; + } + + /** + * 获取 ID 字段映射 + * + * @return ID 字段映射 + */ + public FieldColumn getIdFieldColumn() { + return idFieldColumn; + } + + /** + * 获取字段映射列表 + * + * @return 字段映射列表 + */ + public List getFieldColumnList() { + return fieldColumnList; + } + + /** + * 是否可创建 + * + * @return true 为可创建 + */ + public boolean canCreate() { + return canCreate; + } + + /** + * 是否可更新 + * + * @return true 为可更新 + */ + public boolean canUpdate() { + return canUpdate; + } + + /** + * 是否可删除 + * + * @return true 为可删除 + */ + public boolean canDelete() { + return canDelete; + } + + /** + * 是否可销毁 + * + * @return true 为可销毁 + */ + public boolean canDestroy() { + return canDestroy; + } + } + + /** + * 实体字段属性 + * + * @author 夜雨 + * @since 2025-02-07 09:54 + */ + protected static class FieldColumn { + + /** 字段 */ + final Field field; + + /** 字段名 */ + final String fieldName; + + /** 列名 */ + final String columnName; + + /** true 为 ID */ + final boolean isId; + + /** true 为自动生成 UUID */ + final boolean isAutoUUID; + + final boolean isAutoUpperUUID; + + final boolean isDeleteColumn; + + final DeleteColumn.Type deleteColumnType; + + /** + * 创建字段映射 + * + * @param field 字段 + */ + public FieldColumn(Field field) { + this.field = field; + + fieldName = field.getName(); + Column column = field.getAnnotation(Column.class); + if (column == null) { + columnName = Text.camelCase2underscore(field.getName()); + } else { + columnName = column.value(); + TimiException.required(columnName, "empty field:%s column annotation value for %s entity".formatted(field.getName(), field.getDeclaringClass())); + } + isId = field.isAnnotationPresent(Id.class); + isAutoUUID = field.isAnnotationPresent(AutoUUID.class); + if (isAutoUUID) { + isAutoUpperUUID = field.getAnnotation(AutoUUID.class).upper(); + } else { + isAutoUpperUUID = false; + } + isDeleteColumn = field.isAnnotationPresent(DeleteColumn.class); + if (isDeleteColumn) { + deleteColumnType = field.getAnnotation(DeleteColumn.class).value(); + } else { + deleteColumnType = null; + } + } + + /** + * 判断字段值是否为空 + * + * @param entity 实体 + * @return true 为 null + */ + public boolean isNull(Object entity) { + try { + return Ref.getFieldValue(entity, field, Object.class) == null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * 判断字段值是否非空 + * + * @param entity 实体 + * @return true 为非 null + */ + public boolean isNotNull(Object entity) { + return !isNull(entity); + } + + /** + * 判断字段值是否为空 + * + * @param entity 实体 + * @return true 为空 + */ + public boolean isEmpty(Object entity) { + try { + return TimiJava.isEmpty(Ref.getFieldValue(entity, field, Object.class)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * 判断字段值是否非空 + * + * @param entity 实体 + * @return true 为非空 + */ + public boolean isNotEmpty(Object entity) { + return !isEmpty(entity); + } + + /** + * 获取字段字符串值 + * + * @param obj 实体 + * @return 字符串值 + */ + public String getAsString(Object obj) { + try { + return field.get(obj).toString(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取字段 + * + * @return 字段 + */ + public Field getField() { + return field; + } + + /** + * 获取字段名 + * + * @return 字段名 + */ + public String getFieldName() { + return fieldName; + } + + /** + * 获取列名 + * + * @return 列名 + */ + public String getColumnName() { + return columnName; + } + + /** + * 是否为 ID 字段 + * + * @return true 为 ID 字段 + */ + public boolean isId() { + return isId; + } + + /** + * 是否非 ID 字段 + * + * @return true 为非 ID 字段 + */ + public boolean isNotId() { + return !isId(); + } + + /** + * 是否自动 UUID + * + * @return true 为自动 UUID + */ + public boolean isAutoUUID() { + return isAutoUUID; + } + + /** + * 是否自动大写 UUID + * + * @return true 为自动大写 UUID + */ + public boolean isAutoUpperUUID() { + return isAutoUpperUUID; + } + } +} diff --git a/src/main/java/com/imyeyu/spring/util/DynamicTableSQLProvider.java b/src/main/java/com/imyeyu/spring/util/DynamicTableSQLProvider.java new file mode 100644 index 0000000..e86648c --- /dev/null +++ b/src/main/java/com/imyeyu/spring/util/DynamicTableSQLProvider.java @@ -0,0 +1,175 @@ +package com.imyeyu.spring.util; + +import com.imyeyu.spring.bean.Page; +import com.imyeyu.spring.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.builder.annotation.ProviderContext; + +/** + * 支持动态表名的 SQL 提供器 + *

继承自 {@link BaseSQLProvider},为 {@link com.imyeyu.spring.mapper.DynamicTableMapper DynamicTableMapper} 提供适配层

+ * + * @author 夜雨 + * @since 2026-01-07 11:10 + */ +public class DynamicTableSQLProvider extends BaseSQLProvider { + + /** + * 根据 Page 对象查询数据列表 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param page 分页参数 + * @return SQL + */ + public String selectByPage(ProviderContext context, @Param("tableName") String tableName, @Param("page") Page page) { + EntityMeta meta = getEntityMeta(context); + return buildSelectByPageSQL(meta, tableName, page, "#{page.offset}", "#{page.limit}"); + } + + /** + * 根据 Page 对象统计数据量 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param page 分页参数 + * @return SQL + */ + public String countByPage(ProviderContext context, @Param("tableName") String tableName, @Param("page") Page page) { + EntityMeta meta = getEntityMeta(context); + return buildCountByPageSQL(meta, tableName, page); + } + + /** + * 查询全部数据 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @return SQL + */ + public String selectAll(ProviderContext context, @Param("tableName") String tableName) { + EntityMeta meta = getEntityMeta(context); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT * FROM `%s` WHERE 1 = 1".formatted(tableName)); + if (meta.canDelete) { + sql.append(BaseMapper.NOT_DELETE); + } + return sql.toString(); + } + + /** + * 插入 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param entity 实体 + * @return SQL + */ + public String insert(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity) { + EntityMeta meta = getEntityMeta(context); + return buildInsertSQL(meta, tableName, entity, "entity."); + } + + /** + * 根据 ID 查询 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param id ID + * @return SQL + */ + public String select(ProviderContext context, @Param("tableName") String tableName, @Param("id") Object id) { + EntityMeta meta = getEntityMeta(context); + return buildSelectByIdSQL(meta, tableName, "id"); + } + + /** + * 根据实体非空字段使用等号查询 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param entity 实体 + * @return SQL + */ + public String selectByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity) { + return selectAllByExample(context, tableName, entity) + BaseMapper.LIMIT_1; + } + + /** + * 根据实体非空字段使用等号查询 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param entity 实体 + * @return SQL + */ + public String selectAllByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity) { + EntityMeta meta = getEntityMeta(context); + return buildSelectAllByExampleSQL(meta, tableName, entity, "entity."); + } + + /** + * 根据 ID 更新 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param entity 实体 + * @return SQL + */ + public String update(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity) { + EntityMeta meta = getEntityMeta(context); + return buildUpdateSQL(meta, tableName, entity, "entity."); + } + + /** + * 根据 ID 更新,选择性更新非空属性 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param entity 实体 + * @return SQL + */ + public String updateSelective(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity) { + EntityMeta meta = getEntityMeta(context); + return buildUpdateSelectiveSQL(meta, tableName, entity, "entity."); + } + + /** + * 根据 ID 软删除 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param id ID + * @return SQL + */ + public String delete(ProviderContext context, @Param("tableName") String tableName, @Param("id") Object id) { + EntityMeta meta = getEntityMeta(context); + return buildDeleteSQL(meta, tableName, "id"); + } + + /** + * 根据示例批量逻辑删除 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param entity 实体 + * @return SQL + */ + public String deleteAllByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity) { + EntityMeta meta = getEntityMeta(context); + return buildDeleteAllByExampleSQL(meta, tableName, entity, "entity."); + } + + /** + * 硬删除 + * + * @param context 代理器上下文 + * @param tableName 表名 + * @param id ID + * @return SQL + */ + public String destroy(ProviderContext context, @Param("tableName") String tableName, @Param("id") Object id) { + EntityMeta meta = getEntityMeta(context); + return buildDestroySQL(meta, tableName, "id"); + } +} diff --git a/src/main/java/com/imyeyu/spring/util/SQLProvider.java b/src/main/java/com/imyeyu/spring/util/SQLProvider.java index 2602d19..ad910c5 100644 --- a/src/main/java/com/imyeyu/spring/util/SQLProvider.java +++ b/src/main/java/com/imyeyu/spring/util/SQLProvider.java @@ -1,49 +1,18 @@ package com.imyeyu.spring.util; -import com.imyeyu.java.TimiJava; -import com.imyeyu.java.bean.CallbackArgReturn; -import com.imyeyu.java.bean.timi.TimiCode; -import com.imyeyu.java.bean.timi.TimiException; -import com.imyeyu.java.ref.Ref; -import com.imyeyu.spring.annotation.table.AutoUUID; -import com.imyeyu.spring.annotation.table.Column; -import com.imyeyu.spring.annotation.table.DeleteColumn; -import com.imyeyu.spring.annotation.table.Id; -import com.imyeyu.spring.annotation.table.PageIgnore; -import com.imyeyu.spring.annotation.table.Table; -import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.bean.Page; -import com.imyeyu.spring.entity.Creatable; -import com.imyeyu.spring.entity.Deletable; -import com.imyeyu.spring.entity.Destroyable; -import com.imyeyu.spring.entity.Updatable; import com.imyeyu.spring.mapper.BaseMapper; -import com.imyeyu.utils.Text; -import com.imyeyu.utils.Time; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.builder.annotation.ProviderContext; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - /** * 通用 Mapper SQL 代理器 + *

继承 {@link BaseSQLProvider},为 {@link BaseMapper} 提供适配层

* * @author 夜雨 * @since 2025-02-05 23:34 */ -public class SQLProvider { - - /** 反射缓存 */ - private static final Map, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>(); +public class SQLProvider extends BaseSQLProvider { /** * 创建 SQL 提供器 @@ -60,57 +29,7 @@ public class SQLProvider { */ public String selectByPage(ProviderContext context, @Param("page") Page page) { EntityMeta meta = getEntityMeta(context); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectPageClause, meta.table)); - if (meta.canDelete) { - sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); - } - if (TimiJava.isNotEmpty(page.getEqualsExample())) { - // 精准查询 - Object obj = page.getEqualsExample(); - EntityMeta metaExample = getEntityMeta(obj.getClass()); - String conditionClause = metaExample.fieldColumnList.stream() - .filter(fc -> fc.isNotEmpty(obj)) - .map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj))) - .collect(Collectors.joining(" AND ")); - if (TimiJava.isNotEmpty(conditionClause)) { - sql.append(" AND ").append(conditionClause); - } - } - if (TimiJava.isNotEmpty(page.getLikesExample())) { - // 模糊查询 - Object obj = page.getLikesExample(); - EntityMeta metaExample = getEntityMeta(obj.getClass()); - String conditionClause = metaExample.fieldColumnList.stream() - .filter(fc -> fc.isNotEmpty(obj)) - .map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj))) - .collect(Collectors.joining(" OR ")); - if (TimiJava.isNotEmpty(conditionClause)) { - sql.append(" AND (").append(conditionClause).append(')'); - } - } - // 排序 - if (TimiJava.isNotEmpty(page.getOrderMap())) { - sql.append(" ORDER BY "); - for (Map.Entry item : page.getOrderMap().entrySet()) { - sql.append("`%s` %s, ".formatted( - Text.camelCase2underscore(item.getKey()), - item.getValue().toString() - )); - } - sql.deleteCharAt(sql.length() - 2); - } else { - // 默认排序 - if (meta.canCreate && !meta.canUpdate) { - sql.append(" ORDER BY `created_at` DESC"); - } - if (meta.canCreate && meta.canUpdate) { - sql.append(" ORDER BY COALESCE(`updated_at`, `created_at`) DESC"); - } - } - // 分页 - sql.append(" LIMIT #{offset}, #{limit}"); - return sql.toString(); + return buildSelectByPageSQL(meta, meta.table, page, "#{offset}", "#{limit}"); } /** @@ -122,36 +41,7 @@ public class SQLProvider { */ public String countByPage(ProviderContext context, @Param("page") Page page) { EntityMeta meta = getEntityMeta(context); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT COUNT(*) FROM `%s` WHERE 1 = 1".formatted(meta.table)); - if (meta.canDelete) { - sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); - } - if (TimiJava.isNotEmpty(page.getEqualsExample())) { - // 精准查询 - Object obj = page.getEqualsExample(); - EntityMeta metaExample = getEntityMeta(obj.getClass()); - String conditionClause = metaExample.fieldColumnList.stream() - .filter(fc -> fc.isNotEmpty(obj)) - .map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj))) - .collect(Collectors.joining(" AND ")); - if (TimiJava.isNotEmpty(conditionClause)) { - sql.append(" AND ").append(conditionClause); - } - } - if (TimiJava.isNotEmpty(page.getLikesExample())) { - // 模糊查询 - Object obj = page.getLikesExample(); - EntityMeta metaExample = getEntityMeta(obj.getClass()); - String conditionClause = metaExample.fieldColumnList.stream() - .filter(fc -> fc.isNotEmpty(obj)) - .map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj))) - .collect(Collectors.joining(" OR ")); - if (TimiJava.isNotEmpty(conditionClause)) { - sql.append(" AND (").append(conditionClause).append(')'); - } - } - return sql.toString(); + return buildCountByPageSQL(meta, meta.table, page); } /** @@ -172,7 +62,7 @@ public class SQLProvider { /** * 插入 - *

不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程

+ *

不实现 {@link com.imyeyu.spring.entity.Creatable Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程

* * @param context 代理器上下文 * @param entity 实体 @@ -180,25 +70,7 @@ public class SQLProvider { */ public String insert(ProviderContext context, Object entity) { EntityMeta meta = getEntityMeta(entity.getClass()); - String columns = meta.fieldColumnList.stream().map(fc -> "`%s`".formatted(fc.columnName)).collect(Collectors.joining(", ")); - String values = meta.fieldColumnList.stream().map(fc -> { - try { - if (fc.isAutoUUID && TimiJava.isEmpty(Ref.getFieldValue(entity, fc.field, String.class))) { - String uuid = UUID.randomUUID().toString(); - if (fc.isAutoUpperUUID) { - uuid = uuid.toUpperCase(); - } - Ref.setFieldValue(entity, fc.field, uuid); - } - } catch (IllegalAccessException e) { - throw new TimiException(TimiCode.ERROR).msgKey("auto set field:%s value error".formatted(fc.fieldName)); - } - if (entity instanceof Creatable creatableEntity && creatableEntity.getCreatedAt() == null) { - creatableEntity.setCreatedAt(Time.now()); - } - return "#{%s}".formatted(fc.fieldName); - }).collect(Collectors.joining(", ")); - return "INSERT INTO `%s` (%s) VALUES (%s)".formatted(meta.table, columns, values); + return buildInsertSQL(meta, meta.table, entity, ""); } /** @@ -210,14 +82,7 @@ public class SQLProvider { */ public String select(ProviderContext context, @Param("id") Object id) { EntityMeta meta = getEntityMeta(context); - TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); - - StringBuilder sql = new StringBuilder(); - sql.append("SELECT %s FROM `%s` WHERE `%s` = #{%s}".formatted(meta.selectAllClause, meta.table, meta.idFieldColumn.columnName, id)); - if (meta.canDelete) { - sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); - } - return sql.append(" LIMIT 1").toString(); + return buildSelectByIdSQL(meta, meta.table, "id"); } /** @@ -238,67 +103,33 @@ public class SQLProvider { */ public String selectAllByExample(Object entity) { EntityMeta meta = getEntityMeta(entity.getClass()); - String conditionClause = meta.fieldColumnList.stream() - .filter(fc -> fc.isNotEmpty(entity)) - .map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName)) - .collect(Collectors.joining(" AND ")); - - StringBuilder sql = new StringBuilder(); - sql.append("SELECT %s FROM `%s` WHERE %s".formatted(meta.selectAllClause, meta.table, conditionClause)); - if (meta.canDelete) { - if (TimiJava.isNotEmpty(conditionClause)) { - sql.append(" AND "); - } - sql.append("(`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); - } - return sql.toString(); + return buildSelectAllByExampleSQL(meta, meta.table, entity, ""); } /** - * 根据 ID 更新,需要实体实现 {@link Updatable} + * 根据 ID 更新,需要实体实现 {@link com.imyeyu.spring.entity.Updatable Updatable} * * @param entity 实体 * @return SQL */ public String update(Object entity) { EntityMeta meta = getEntityMeta(entity.getClass()); - TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); - TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass)); - - if (entity instanceof Updatable updatable) { - updatable.setUpdatedAt(Time.now()); - } - String setClause = meta.fieldColumnList.stream() - .filter(FieldColumn::isNotId) - .map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName)) - .collect(Collectors.joining(", ")); - return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName); + return buildUpdateSQL(meta, meta.table, entity, ""); } /** - * 根据 ID 更新,选择性更新非空属性,需要实体实现 {@link Updatable} + * 根据 ID 更新,选择性更新非空属性,需要实体实现 {@link com.imyeyu.spring.entity.Updatable Updatable} * * @param entity 实体 * @return SQL */ public String updateSelective(Object entity) { EntityMeta meta = getEntityMeta(entity.getClass()); - TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); - TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass)); - - if (entity instanceof Updatable updatable) { - updatable.setUpdatedAt(Time.now()); - } - String setClause = meta.fieldColumnList.stream() - .filter(FieldColumn::isNotId) - .filter(fc -> fc.isNotNull(entity)) - .map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName)) - .collect(Collectors.joining(", ")); - return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName); + return buildUpdateSelectiveSQL(meta, meta.table, entity, ""); } /** - * 根据 ID 软删除,需要实体实现 {@link Deletable} + * 根据 ID 软删除,需要实体实现 {@link com.imyeyu.spring.entity.Deletable Deletable} * * @param context 代理器上下文 * @param id ID @@ -306,10 +137,7 @@ public class SQLProvider { */ public String delete(ProviderContext context, @Param("id") Object id) { EntityMeta meta = getEntityMeta(context); - TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); - TimiException.requiredTrue(meta.canDelete, "not allow soft delete for %s".formatted(meta.entityClass)); - - return "UPDATE `%s` SET `deleted_at` = %s WHERE `%s` = #{id}".formatted(meta.table, Time.now(), meta.idFieldColumn.columnName); + return buildDeleteSQL(meta, meta.table, "id"); } /** @@ -320,29 +148,11 @@ public class SQLProvider { */ public String deleteAllByExample(Object entity) { EntityMeta meta = getEntityMeta(entity.getClass()); - TimiException.required(meta.canDelete, "not allow delete for %s".formatted(meta.entityClass)); - - FieldColumn deleteColumn = meta.getFieldColumnList().stream().filter(fc -> fc.isDeleteColumn).findFirst().orElse(null); - TimiException.required(deleteColumn, "unknown delete column, use com.imyeyu.spring.annotation.table.DeleteColumn annotation on field"); - assert deleteColumn != null; - assert deleteColumn.deleteColumnType != null; - - String delClause = meta.fieldColumnList.stream() - .filter(FieldColumn::isNotId) - .filter(fc -> fc.isNotEmpty(entity)) - .map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName)) - .collect(Collectors.joining(" AND ")); - StringBuilder sql = new StringBuilder("UPDATE `%s` SET `%s` = ".formatted(meta.table, deleteColumn.getColumnName())); - sql.append("'").append(switch (deleteColumn.deleteColumnType) { - case UNIX -> Time.now(); - case DATE, DATE_TIME -> new Date(); - }).append("'"); - sql.append(" WHERE ").append(delClause); - return sql.toString(); + return buildDeleteAllByExampleSQL(meta, meta.table, entity, ""); } /** - * 硬删除,需要实体实现 {@link Destroyable} + * 硬删除,需要实体实现 {@link com.imyeyu.spring.entity.Destroyable Destroyable} * * @param context 代理器上下文 * @param id ID @@ -350,404 +160,6 @@ public class SQLProvider { */ public String destroy(ProviderContext context, @Param("id") Object id) { EntityMeta meta = getEntityMeta(context); - TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass)); - TimiException.requiredTrue(meta.canDestroy, "not allow destroy for %s".formatted(meta.entityClass)); - - return "DELETE FROM `%s` WHERE `%s` = #{id}".formatted(meta.table, meta.idFieldColumn.columnName); - } - - /** - * 根据代理器上下文获取 Mapper 实体类元数据 - * - * @param context 代理器上下文 - * @return 实体类元数据 - */ - protected EntityMeta getEntityMeta(ProviderContext context) { - Type[] types = context.getMapperType().getGenericInterfaces(); - ParameterizedType type = (ParameterizedType) types[0]; - Class entityClass = (Class) type.getActualTypeArguments()[0]; - return getEntityMeta(entityClass); - } - - /** - * 获取实体类元数据 - * - * @param entityClass 实体类 - * @return 元数据 - */ - protected EntityMeta getEntityMeta(Class entityClass) { - return ENTITY_META_CACHE.computeIfAbsent(entityClass, EntityMeta::new); - } - - /** - * 实体元数据 - * - * @author 夜雨 - * @since 2025-02-05 23:47 - */ - protected static class EntityMeta { - - /** 实体类 */ - final Class entityClass; - - /** 表名 */ - final String table; - - /** 查询字段映射 */ - final String selectAllClause; - - /** 页面查询字段映射 */ - final String selectPageClause; - - /** ID 字段 */ - final FieldColumn idFieldColumn; - - /** 只读的列名字段名映射,Map<列名,字段名> */ - final List fieldColumnList; - - /** true 为可创建 */ - final boolean canCreate; - - /** true 为可更新 */ - final boolean canUpdate; - - /** true 为可删除(软删除) */ - final boolean canDelete; - - /** true 为可销毁(硬删除) */ - final boolean canDestroy; - - /** - * 创建实体元数据 - * - * @param entityClass 实体类型 - */ - public EntityMeta(Class entityClass) { - this.entityClass = entityClass; - - // 表名 - while (entityClass.isAnnotationPresent(Transient.class)) { - entityClass = entityClass.getSuperclass(); - } - Table table = entityClass.getAnnotation(Table.class); - if (table == null) { - this.table = Text.camelCase2underscore(entityClass.getSimpleName()); - } else { - this.table = table.value(); - TimiException.required(this.table, String.format("empty table annotation value for %s entity", entityClass.getName())); - } - List allFieldList = Ref.listAllFields(entityClass); - FieldColumn idFieldColumn = null; - List fieldColumnList = new ArrayList<>(); - for (int i = 0; i < allFieldList.size(); i++) { - Field field = allFieldList.get(i); - if (field.isAnnotationPresent(Transient.class)) { - continue; - } - FieldColumn fieldColumn = new FieldColumn(field); - if (fieldColumn.isId) { - TimiException.requiredNull(idFieldColumn, String.format("multi id field for %s entity", entityClass.getName())); - idFieldColumn = fieldColumn; - } - fieldColumnList.add(fieldColumn); - } - this.selectAllClause = buildSelectClause(fieldColumnList, null); - this.selectPageClause = buildSelectClause(fieldColumnList, fc -> !fc.getField().isAnnotationPresent(PageIgnore.class)); - this.idFieldColumn = idFieldColumn; - this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读 - canCreate = Creatable.class.isAssignableFrom(entityClass); - canUpdate = Updatable.class.isAssignableFrom(entityClass); - canDelete = Deletable.class.isAssignableFrom(entityClass); - canDestroy = Destroyable.class.isAssignableFrom(entityClass); - } - - private String buildSelectClause(List fieldColumnList, CallbackArgReturn callback) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < fieldColumnList.size(); i++) { - FieldColumn fieldColumn = fieldColumnList.get(i); - Field field = fieldColumn.getField(); - if (callback != null && !callback.handler(fieldColumn)) { - continue; - } - Column column = field.getAnnotation(Column.class); - if (column == null) { - sb.append('`').append(fieldColumn.columnName).append('`'); - sb.append(','); - } else { - // 自定义映射列名 - sb.append('`').append(column.value()).append('`'); - sb.append(" AS `").append(fieldColumn.fieldName).append('`'); - sb.append(','); - } - } - return sb.substring(0, sb.length() - 1); - } - - /** - * 获取实体类型 - * - * @return 实体类型 - */ - public Class getEntityClass() { - return entityClass; - } - - /** - * 获取表名 - * - * @return 表名 - */ - public String getTable() { - return table; - } - - /** - * 获取查询字段映射 - * - * @return 查询字段映射 - */ - public String getSelectAllClause() { - return selectAllClause; - } - - /** - * 获取 ID 字段映射 - * - * @return ID 字段映射 - */ - public FieldColumn getIdFieldColumn() { - return idFieldColumn; - } - - /** - * 获取字段映射列表 - * - * @return 字段映射列表 - */ - public List getFieldColumnList() { - return fieldColumnList; - } - - /** - * 是否可创建 - * - * @return true 为可创建 - */ - public boolean canCreate() { - return canCreate; - } - - /** - * 是否可更新 - * - * @return true 为可更新 - */ - public boolean canUpdate() { - return canUpdate; - } - - /** - * 是否可删除 - * - * @return true 为可删除 - */ - public boolean canDelete() { - return canDelete; - } - - /** - * 是否可销毁 - * - * @return true 为可销毁 - */ - public boolean canDestroy() { - return canDestroy; - } - } - - /** - * 实体字段属性 - * - * @author 夜雨 - * @since 2025-02-07 09:54 - */ - protected static class FieldColumn { - - /** 字段 */ - final Field field; - - /** 字段名 */ - final String fieldName; - - /** 列名 */ - final String columnName; - - /** true 为 ID */ - final boolean isId; - - /** true 为自动生成 UUID */ - final boolean isAutoUUID; - - final boolean isAutoUpperUUID; - - final boolean isDeleteColumn; - - final DeleteColumn.Type deleteColumnType; - - /** - * 创建字段映射 - * - * @param field 字段 - */ - public FieldColumn(Field field) { - this.field = field; - - fieldName = field.getName(); - Column column = field.getAnnotation(Column.class); - if (column == null) { - columnName = Text.camelCase2underscore(field.getName()); - } else { - columnName = column.value(); - TimiException.required(columnName, "empty field:%s column annotation value for %s entity".formatted(field.getName(), field.getDeclaringClass())); - } - isId = field.isAnnotationPresent(Id.class); - isAutoUUID = field.isAnnotationPresent(AutoUUID.class); - if (isAutoUUID) { - isAutoUpperUUID = field.getAnnotation(AutoUUID.class).upper(); - } else { - isAutoUpperUUID = false; - } - isDeleteColumn = field.isAnnotationPresent(DeleteColumn.class); - if (isDeleteColumn) { - deleteColumnType = field.getAnnotation(DeleteColumn.class).value(); - } else { - deleteColumnType = null; - } - } - - /** - * 判断字段值是否为空 - * - * @param entity 实体 - * @return true 为 null - */ - public boolean isNull(Object entity) { - try { - return Ref.getFieldValue(entity, field, Object.class) == null; - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - /** - * 判断字段值是否非空 - * - * @param entity 实体 - * @return true 为非 null - */ - public boolean isNotNull(Object entity) { - return !isNull(entity); - } - - /** - * 判断字段值是否为空 - * - * @param entity 实体 - * @return true 为空 - */ - public boolean isEmpty(Object entity) { - try { - return TimiJava.isEmpty(Ref.getFieldValue(entity, field, Object.class)); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - /** - * 判断字段值是否非空 - * - * @param entity 实体 - * @return true 为非空 - */ - public boolean isNotEmpty(Object entity) { - return !isEmpty(entity); - } - - /** - * 获取字段字符串值 - * - * @param obj 实体 - * @return 字符串值 - */ - public String getAsString(Object obj) { - try { - return field.get(obj).toString(); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - /** - * 获取字段 - * - * @return 字段 - */ - public Field getField() { - return field; - } - - /** - * 获取字段名 - * - * @return 字段名 - */ - public String getFieldName() { - return fieldName; - } - - /** - * 获取列名 - * - * @return 列名 - */ - public String getColumnName() { - return columnName; - } - - /** - * 是否为 ID 字段 - * - * @return true 为 ID 字段 - */ - public boolean isId() { - return isId; - } - - /** - * 是否非 ID 字段 - * - * @return true 为非 ID 字段 - */ - public boolean isNotId() { - return !isId(); - } - - /** - * 是否自动 UUID - * - * @return true 为自动 UUID - */ - public boolean isAutoUUID() { - return isAutoUUID; - } - - /** - * 是否自动大写 UUID - * - * @return true 为自动大写 UUID - */ - public boolean isAutoUpperUUID() { - return isAutoUpperUUID; - } + return buildDestroySQL(meta, meta.table, "id"); } }