Files
timi-spring/src/main/java/com/imyeyu/spring/util/BaseSQLProvider.java
2026-03-16 15:42:25 +08:00

716 lines
22 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 提供器基类
* <p>包含所有 SQL 构建逻辑和实体元数据管理</p>
*
* @author 夜雨
* @since 2026-01-07 11:20
*/
public abstract class BaseSQLProvider {
/** 反射缓存 */
private static final Map<Class<?>, 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<String, BaseMapper.OrderType> 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&lt;列名,字段名&gt; */
final List<FieldColumn> 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<Field> allFieldList = Ref.listAllFields(entityClass);
FieldColumn idFieldColumn = null;
List<FieldColumn> 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<FieldColumn> fieldColumnList, CallbackArgReturn<FieldColumn, Boolean> 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();
}
}
}