Compare commits

..

3 Commits

Author SHA1 Message Date
7cd79bff55 add example Logic 2026-01-15 17:22:47 +08:00
fd7bb73f5c add destroyAllByExample 2026-01-15 11:20:53 +08:00
7ac4cdae56 add DynamicTableMapper support and refactor SQL provider architecture
新增动态表名支持和 SQL 提供器架构重构,用于支持分表等动态表名场景:

- 新增 DynamicTableMapper 接口,所有方法支持显式传入表名参数
- 新增 BaseSQLProvider 基类,包含所有 SQL 构建逻辑和实体元数据管理
- 重构 SQLProvider 为 BaseSQLProvider 的适配层,专注于 BaseMapper 参数适配
- 新增 DynamicTableSQLProvider 适配层,提供动态表名的 SQL 构建能力
- 通过参数前缀 (paramPrefix) 统一处理不同的参数绑定方式
- 消除所有代码重复,SQL 构建逻辑统一到基类
- 版本升级到 0.0.2

架构优势:单一职责、零重复、易扩展、易维护

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-07 16:12:52 +08:00
10 changed files with 1392 additions and 620 deletions

View File

@ -13,7 +13,7 @@
<groupId>com.imyeyu.spring</groupId>
<artifactId>timi-spring</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
<packaging>jar</packaging>
<properties>

View File

@ -0,0 +1,16 @@
package com.imyeyu.spring.bean;
/**
* 示例连接逻辑
*
* @author 夜雨
* @since 2026-01-15 11:19
*/
public enum Logic {
/** 且 */
AND,
/** 或 */
OR
}

View File

@ -22,6 +22,12 @@ public class Page<T> extends BasePage {
/** 模糊匹配示例 */
protected T likesExample;
/** 精确匹配连接逻辑 */
protected Logic equalsLogic = Logic.AND;
/** 模糊匹配连接逻辑 */
protected Logic likesLogic = Logic.OR;
/** 排序字段映射 */
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
@ -95,6 +101,42 @@ public class Page<T> extends BasePage {
this.likesExample = likesExample;
}
/**
* 获取精确匹配连接逻辑
*
* @return 连接逻辑
*/
public Logic getEqualsLogic() {
return equalsLogic;
}
/**
* 设置精确匹配连接逻辑
*
* @param equalsLogic 连接逻辑
*/
public void setEqualsLogic(Logic equalsLogic) {
this.equalsLogic = equalsLogic;
}
/**
* 获取模糊匹配连接逻辑
*
* @return 连接逻辑
*/
public Logic getLikesLogic() {
return likesLogic;
}
/**
* 设置模糊匹配连接逻辑
*
* @param likesLogic 连接逻辑
*/
public void setLikesLogic(Logic likesLogic) {
this.likesLogic = likesLogic;
}
/**
* 获取排序映射
*

View File

@ -1,11 +1,13 @@
package com.imyeyu.spring.mapper;
import com.imyeyu.spring.bean.Logic;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.util.SQLProvider;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
@ -111,8 +113,19 @@ public interface BaseMapper<T, P> {
* @param t 示例对象
* @return 数据对象
*/
default T selectByExample(T t) {
return selectByExample(t, Logic.AND);
}
/**
* 根据示例查询单条数据
*
* @param t 示例对象
* @param logic 条件连接逻辑
* @return 数据对象
*/
@SelectProvider(type = SQLProvider.class, method = "selectByExample")
T selectByExample(T t);
T selectByExample(@Param("entity") T t, @Param("logic") Logic logic);
/**
* 根据示例查询全部数据
@ -120,8 +133,19 @@ public interface BaseMapper<T, P> {
* @param t 示例对象
* @return 数据列表
*/
default List<T> selectAllByExample(T t) {
return selectAllByExample(t, Logic.AND);
}
/**
* 根据示例查询全部数据
*
* @param t 示例对象
* @param logic 条件连接逻辑
* @return 数据列表
*/
@SelectProvider(type = SQLProvider.class, method = "selectAllByExample")
List<T> selectAllByExample(T t);
List<T> selectAllByExample(@Param("entity") T t, @Param("logic") Logic logic);
/**
* 修改数据
@ -152,8 +176,18 @@ public interface BaseMapper<T, P> {
*
* @param t 示例对象
*/
default void deleteAllByExample(T t) {
deleteAllByExample(t, Logic.AND);
}
/**
* 根据示例批量逻辑删除
*
* @param t 示例对象
* @param logic 条件连接逻辑
*/
@UpdateProvider(type = SQLProvider.class, method = "deleteAllByExample")
void deleteAllByExample(T t);
void deleteAllByExample(@Param("entity") T t, @Param("logic") Logic logic);
/**
* 销毁(物理删除)
@ -162,4 +196,22 @@ public interface BaseMapper<T, P> {
*/
@DeleteProvider(type = SQLProvider.class, method = "destroy")
void destroy(P id);
/**
* 根据示例批量销毁(物理删除)
*
* @param t 示例对象
*/
default void destroyAllByExample(T t) {
destroyAllByExample(t, Logic.AND);
}
/**
* 根据示例批量销毁(物理删除)
*
* @param t 示例对象
* @param logic 条件连接逻辑
*/
@DeleteProvider(type = SQLProvider.class, method = "destroyAllByExample")
void destroyAllByExample(@Param("entity") T t, @Param("logic") Logic logic);
}

View File

@ -0,0 +1,209 @@
package com.imyeyu.spring.mapper;
import com.imyeyu.spring.bean.Logic;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.util.DynamicTableSQLProvider;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import java.util.List;
/**
* 支持动态表名的 SQL 映射接口
* <p>相比 {@link BaseMapper},所有方法都需要显式传入表名参数</p>
*
* @param <T> 实体类型
* @param <P> 主键类型
* @author 夜雨
* @since 2026-01-07 11:00
*/
public interface DynamicTableMapper<T, P> {
/**
* 根据 Page 对象查询数据列表
*
* @param tableName 表名
* @param page 分页参数
* @return 数据列表
*/
@SelectProvider(type = DynamicTableSQLProvider.class, method = "selectByPage")
List<T> selectByPage(@Param("tableName") String tableName, @Param("page") Page<T> page);
/**
* 根据 Page 对象统计数据量
*
* @param tableName 表名
* @param page 分页参数
* @return 数据量
*/
@SelectProvider(type = DynamicTableSQLProvider.class, method = "countByPage")
long countByPage(@Param("tableName") String tableName, @Param("page") Page<T> page);
/**
* 分页查询
*
* @param tableName 表名
* @param page 分页参数
* @return 分页结果
*/
default PageResult<T> selectPageResult(String tableName, Page<T> page) {
PageResult<T> 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<T> 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 数据对象
*/
default T selectByExample(String tableName, T entity) {
return selectByExample(tableName, entity, Logic.AND);
}
/**
* 根据示例查询单条数据
*
* @param tableName 表名
* @param entity 示例对象
* @param logic 条件连接逻辑
* @return 数据对象
*/
@SelectProvider(type = DynamicTableSQLProvider.class, method = "selectByExample")
T selectByExample(@Param("tableName") String tableName, @Param("entity") T entity, @Param("logic") Logic logic);
/**
* 根据示例查询全部数据
*
* @param tableName 表名
* @param entity 示例对象
* @return 数据列表
*/
default List<T> selectAllByExample(String tableName, T entity) {
return selectAllByExample(tableName, entity, Logic.AND);
}
/**
* 根据示例查询全部数据
*
* @param tableName 表名
* @param entity 示例对象
* @param logic 条件连接逻辑
* @return 数据列表
*/
@SelectProvider(type = DynamicTableSQLProvider.class, method = "selectAllByExample")
List<T> selectAllByExample(@Param("tableName") String tableName, @Param("entity") T entity, @Param("logic") Logic logic);
/**
* 修改数据
*
* @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 示例对象
*/
default void deleteAllByExample(String tableName, T entity) {
deleteAllByExample(tableName, entity, Logic.AND);
}
/**
* 根据示例批量逻辑删除
*
* @param tableName 表名
* @param entity 示例对象
* @param logic 条件连接逻辑
*/
@UpdateProvider(type = DynamicTableSQLProvider.class, method = "deleteAllByExample")
void deleteAllByExample(@Param("tableName") String tableName, @Param("entity") T entity, @Param("logic") Logic logic);
/**
* 销毁(物理删除)
*
* @param tableName 表名
* @param id 索引
*/
@DeleteProvider(type = DynamicTableSQLProvider.class, method = "destroy")
void destroy(@Param("tableName") String tableName, @Param("id") P id);
/**
* 根据示例批量销毁(物理删除)
*
* @param tableName 表名
* @param entity 示例对象
*/
default void destroyAllByExample(String tableName, T entity) {
destroyAllByExample(tableName, entity, Logic.AND);
}
/**
* 根据示例批量销毁(物理删除)
*
* @param tableName 表名
* @param entity 示例对象
* @param logic 条件连接逻辑
*/
@DeleteProvider(type = DynamicTableSQLProvider.class, method = "destroyAllByExample")
void destroyAllByExample(@Param("tableName") String tableName, @Param("entity") T entity, @Param("logic") Logic logic);
}

View File

@ -1,6 +1,8 @@
package com.imyeyu.spring.mapper;
import com.imyeyu.spring.bean.Logic;
import com.imyeyu.spring.util.RawSQLProvider;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import java.util.List;
@ -38,8 +40,19 @@ public interface RawMapper<T, P> {
* @param t 示例对象
* @return 数据对象
*/
default T selectByExampleRaw(T t) {
return selectByExampleRaw(t, Logic.AND);
}
/**
* 根据示例查询单条数据
*
* @param t 示例对象
* @param logic 条件连接逻辑
* @return 数据对象
*/
@SelectProvider(type = RawSQLProvider.class, method = "selectByExample")
T selectByExampleRaw(T t);
T selectByExampleRaw(@Param("entity") T t, @Param("logic") Logic logic);
/**
* 根据示例查询全部数据
@ -47,6 +60,17 @@ public interface RawMapper<T, P> {
* @param t 示例对象
* @return 数据列表
*/
default List<T> selectAllByExampleRaw(T t) {
return selectAllByExampleRaw(t, Logic.AND);
}
/**
* 根据示例查询全部数据
*
* @param t 示例对象
* @param logic 条件连接逻辑
* @return 数据列表
*/
@SelectProvider(type = RawSQLProvider.class, method = "selectAllByExample")
List<T> selectAllByExampleRaw(T t);
List<T> selectAllByExampleRaw(@Param("entity") T t, @Param("logic") Logic logic);
}

View File

@ -0,0 +1,811 @@
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 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
*/
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 实体类型
*/
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<FieldColumn> 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;
}
}
}

View File

@ -0,0 +1,193 @@
package com.imyeyu.spring.util;
import com.imyeyu.spring.bean.Logic;
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 提供器
* <p>继承自 {@link BaseSQLProvider},为 {@link com.imyeyu.spring.mapper.DynamicTableMapper DynamicTableMapper} 提供适配层</p>
*
* @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 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String selectByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity, @Param("logic") Logic logic) {
return selectAllByExample(context, tableName, entity, logic) + BaseMapper.LIMIT_1;
}
/**
* 根据实体非空字段使用等号查询
*
* @param context 代理器上下文
* @param tableName 表名
* @param entity 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String selectAllByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity, @Param("logic") Logic logic) {
EntityMeta meta = getEntityMeta(context);
return buildSelectAllByExampleSQL(meta, tableName, entity, "entity.", logic);
}
/**
* 根据 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 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String deleteAllByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity, @Param("logic") Logic logic) {
EntityMeta meta = getEntityMeta(context);
return buildDeleteAllByExampleSQL(meta, tableName, entity, "entity.", logic);
}
/**
* 硬删除
*
* @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");
}
/**
* 根据示例批量物理删除
*
* @param context 代理器上下文
* @param tableName 表名
* @param entity 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String destroyAllByExample(ProviderContext context, @Param("tableName") String tableName, @Param("entity") Object entity, @Param("logic") Logic logic) {
EntityMeta meta = getEntityMeta(context);
return buildDestroyAllByExampleSQL(meta, tableName, entity, "entity.", logic);
}
}

View File

@ -1,7 +1,9 @@
package com.imyeyu.spring.util;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.bean.Logic;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.util.stream.Collectors;
@ -28,22 +30,17 @@ public class RawSQLProvider extends SQLProvider {
}
@Override
public String selectByExample(Object entity) {
return selectAllByExample(entity) + BaseMapper.LIMIT_1;
public String selectByExample(@Param("entity") Object entity, @Param("logic") Logic logic) {
return selectAllByExample(entity, logic) + BaseMapper.LIMIT_1;
}
@Override
public String selectAllByExample(Object entity) {
public String selectAllByExample(@Param("entity") Object entity, @Param("logic") Logic logic) {
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 "));
.map(fc -> "`%s` = #{entity.%s}".formatted(fc.columnName, fc.fieldName))
.collect(Collectors.joining(" %s ".formatted(logic)));
return "SELECT %s FROM `%s` WHERE %s".formatted(meta.selectAllClause, meta.table, conditionClause);
}
@Override
public String deleteAllByExample(Object entity) {
return super.deleteAllByExample(entity);
}
}

View File

@ -1,49 +1,19 @@
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 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 代理器
* <p>继承 {@link BaseSQLProvider},为 {@link BaseMapper} 提供适配层</p>
*
* @author 夜雨
* @since 2025-02-05 23:34
*/
public class SQLProvider {
/** 反射缓存 */
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
public class SQLProvider extends BaseSQLProvider {
/**
* 创建 SQL 提供器
@ -60,57 +30,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<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");
}
}
// 分页
sql.append(" LIMIT #{offset}, #{limit}");
return sql.toString();
return buildSelectByPageSQL(meta, meta.table, page, "#{offset}", "#{limit}");
}
/**
@ -122,36 +42,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 +63,7 @@ public class SQLProvider {
/**
* 插入
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
* <p><i>不实现 {@link com.imyeyu.spring.entity.Creatable Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
*
* @param context 代理器上下文
* @param entity 实体
@ -180,25 +71,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,95 +83,56 @@ 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");
}
/**
* 根据实体非空字段使用等号查询
*
* @param entity 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String selectByExample(Object entity) {
return selectAllByExample(entity) + BaseMapper.LIMIT_1;
public String selectByExample(@Param("entity") Object entity, @Param("logic") Logic logic) {
return selectAllByExample(entity, logic) + BaseMapper.LIMIT_1;
}
/**
* 根据实体非空字段使用等号查询
*
* @param entity 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String selectAllByExample(Object entity) {
public String selectAllByExample(@Param("entity") Object entity, @Param("logic") Logic logic) {
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, "entity.", logic);
}
/**
* 根据 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,43 +140,23 @@ 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");
}
/**
* 根据示例批量逻辑删除
*
* @param entity 实体
* @param logic 条件连接逻辑
* @return SQL
*/
public String deleteAllByExample(Object entity) {
public String deleteAllByExample(@Param("entity") Object entity, @Param("logic") Logic logic) {
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, "entity.", logic);
}
/**
* 硬删除,需要实体实现 {@link Destroyable}
* 硬删除,需要实体实现 {@link com.imyeyu.spring.entity.Destroyable Destroyable}
*
* @param context 代理器上下文
* @param id ID
@ -350,404 +164,18 @@ 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);
return buildDestroySQL(meta, meta.table, "id");
}
/**
* 根据代理器上下文获取 Mapper 实体类元数据
* 根据示例批量销毁(物理删除)
*
* @param context 代理器上下文
* @return 实体类元数据
* @param entity 实体
* @param logic 条件连接逻辑
* @return SQL
*/
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&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 实体类型
*/
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<FieldColumn> 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;
}
public String destroyAllByExample(@Param("entity") Object entity, @Param("logic") Logic logic) {
EntityMeta meta = getEntityMeta(entity.getClass());
return buildDestroyAllByExampleSQL(meta, meta.table, entity, "entity.", logic);
}
}