Files
timi-spring/src/main/java/com/imyeyu/spring/util/SQLProvider.java
2025-10-13 10:53:55 +08:00

368 lines
13 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.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<Class<?>, 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<String, BaseMapper.OrderType> 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<String, BaseMapper.OrderType> 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();
}
/**
* 插入
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
*
* @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&lt;列名,字段名&gt; */
final List<FieldColumn> 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<Field> allFieldList = Ref.listAllFields(entityClass);
StringBuilder selectAllClause = new StringBuilder();
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;
}
{
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;
}
}
}
}