package com.imyeyu.spring.util; import com.imyeyu.java.TimiJava; 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.Id; import com.imyeyu.spring.annotation.table.Table; import com.imyeyu.spring.annotation.table.Transient; 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.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * 通用 Mapper SQL 代理器 * * @author 夜雨 * @since 2025-02-05 23:34 */ public class SQLProvider { /** 反射缓存 */ private static final Map, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>(); public String count(ProviderContext context) { 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())); } return sql.toString(); } public String listOrder(ProviderContext context, @Param("offset") Long offset, @Param("limit") Integer limit, @Param("orderMap") Map orderMap) { EntityMeta meta = getEntityMeta(context); StringBuilder sql = new StringBuilder(); sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectAllClause, meta.table)); if (meta.canDelete) { sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now())); } if (TimiJava.isNotEmpty(orderMap)) { sql.append(" ORDER BY "); for (Map.Entry item : orderMap.entrySet()) { sql.append(Text.camelCase2underscore(item.getKey())).append(' ').append(item.getValue().toString()); sql.append(", "); } sql.deleteCharAt(sql.length() - 2); } return sql.append(" LIMIT %s, %s".formatted(offset, limit)).toString(); } /** * 插入 *

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

* * @param entity 实体 * @return SQL */ 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); } /** * 根据 ID 查询 * * @param context 代理器上下文 * @param id ID * @return SQL */ 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(); } /** * 根据实体非空字段使用等号查询 * * @param entity 实体 * @return SQL */ public String selectByExample(Object entity) { return selectAllByExample(entity) + BaseMapper.LIMIT_1; } /** * 根据实体非空字段使用等号查询 * * @param entity 实体 * @return SQL */ public String selectAllByExample(Object entity) { EntityMeta meta = getEntityMeta(entity.getClass()); String conditionClause = meta.fieldColumnList.stream() .filter(fc -> { try { return Ref.getFieldValue(entity, fc.field, Object.class) != null; } catch (IllegalAccessException e) { throw new RuntimeException(e); } }) .map(fc -> { return "`%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(); } /** * 根据 ID 更新,需要实体实现 {@link 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)); String setClause = meta.fieldColumnList.stream() .filter(fc -> !fc.isId) .map(fc -> { if (entity instanceof Updatable updatableEntity) { updatableEntity.setUpdatedAt(Time.now()); } return "`%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); } /** * 根据 ID 软删除,需要实体实现 {@link Deletable} * * @param context 代理器上下文 * @param id ID * @return SQL */ 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); } /** * 硬删除,需要实体实现 {@link Destroyable} * * @param context 代理器上下文 * @param id ID * @return SQL */ 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 实体类元数据 */ private 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 元数据 */ private EntityMeta getEntityMeta(Class entityClass) { return ENTITY_META_CACHE.computeIfAbsent(entityClass, EntityMeta::new); } /** * 实体元数据 * * @author 夜雨 * @since 2025-02-05 23:47 */ private static class EntityMeta { /** 实体类 */ final Class entityClass; /** 表名 */ final String table; /** 查询字段映射 */ final String selectAllClause; /** ID 字段 */ final FieldColumn idFieldColumn; /** 只读的列名字段名映射,Map<列名,字段名> */ final List fieldColumnList; /** true 为可更新 */ final boolean canUpdate; /** true 为可删除(软删除) */ final boolean canDelete; /** true 为可销毁(硬删除) */ final boolean canDestroy; 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); StringBuilder selectAllClause = new StringBuilder(); 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; } { Column column = field.getAnnotation(Column.class); if (column == null) { selectAllClause.append('`').append(fieldColumn.columnName).append('`'); selectAllClause.append(','); } else { // 处理自定义映射列名 selectAllClause.append('`').append(column.value()).append('`'); selectAllClause.append(" AS `").append(fieldColumn.fieldName).append('`'); selectAllClause.append(','); } } fieldColumnList.add(fieldColumn); } this.selectAllClause = selectAllClause.substring(0, selectAllClause.length() - 1); this.idFieldColumn = idFieldColumn; this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读 canUpdate = Updatable.class.isAssignableFrom(entityClass); canDelete = Deletable.class.isAssignableFrom(entityClass); canDestroy = Destroyable.class.isAssignableFrom(entityClass); } } /** * 实体字段属性 * * @author 夜雨 * @since 2025-02-07 09:54 */ private 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; 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; } } } }