实体类型
+ * @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");
}
}