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.Logic; 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 lombok.Getter; 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(" %s ".formatted(page.getEqualsLogic()))); 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(" %s ".formatted(page.getLikesLogic()))); 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."),空字符串表示无前缀 * @param logic 条件连接逻辑 * @return 条件子句 */ protected String buildExampleConditions(EntityMeta meta, Object entity, String paramPrefix, Logic logic) { return meta.fieldColumnList.stream() .filter(fc -> fc.isNotEmpty(entity)) .map(fc -> "`%s` = #{%s%s}".formatted(fc.columnName, paramPrefix, fc.fieldName)) .collect(Collectors.joining(" %s ".formatted(logic))); } /** * 构建插入 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."),空字符串表示无前缀 * @param logic 条件连接逻辑 * @return SQL */ protected String buildSelectAllByExampleSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix, Logic logic) { String conditionClause = buildExampleConditions(meta, entity, paramPrefix, logic); 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."),空字符串表示无前缀 * @param logic 条件连接逻辑 * @return SQL */ protected String buildDeleteAllByExampleSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix, Logic logic) { 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(" %s ".formatted(logic))); 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); } /** * 构建批量物理删除 SQL * * @param meta 实体元数据 * @param tableName 表名 * @param entity 示例实体 * @param paramPrefix 参数前缀(如 "entity."),空字符串表示无前缀 * @param logic 条件连接逻辑 * @return SQL */ protected String buildDestroyAllByExampleSQL(EntityMeta meta, String tableName, Object entity, String paramPrefix, Logic logic) { TimiException.required(meta.canDestroy, "not allow destroy for %s".formatted(meta.entityClass)); String destroyClause = 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(" %s ".formatted(logic))); return "DELETE FROM `%s` WHERE %s".formatted(tableName, destroyClause); } /** * 实体元数据 * * @author 夜雨 * @since 2025-02-05 23:47 */ @Getter 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 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 */ @Getter 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); } } /** * 是否非 ID 字段 * * @return true 为非 ID 字段 */ public boolean isNotId() { return !isId(); } } }