From 5b6d037961b90dfc45fa5f831457fee98f9f16a7 Mon Sep 17 00:00:00 2001
From: chuck <361648887@qq.com>
Date: Fri, 17 Jul 2026 15:01:49 +0800
Subject: [PATCH 1/9] =?UTF-8?q?refactor(infra):=20=E7=A7=BB=E9=99=A4?=
=?UTF-8?q?=E7=BC=93=E5=AD=98=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE=E5=92=8C?=
=?UTF-8?q?=E4=BE=9D=E8=B5=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 从 InfraProperties 中移除 cacheTime 和 cacheTimeUnit 属性
- 从 Repository 注解中移除 cache、cacheTime 和 cacheTimeUnit 参数
- 从 RepositoryDefinition 中移除缓存相关字段和设置逻辑
- 从 pom.xml 中移除 structure-datascope-cache 依赖
- 从 README.md 中移除缓存相关配置说明
- 从 RepositoryFacade 中移除 DataScopeCacheManager 字段
- 更新 BeanUtils.copyProperties 文档注释添加完整参数说明
---
structure-infra-starter/README.md | 5 ----
structure-infra-starter/pom.xml | 4 ---
.../infra/annotations/Repository.java | 25 ++-----------------
.../infra/properties/InfraProperties.java | 20 ++-------------
.../repository/RepositoryDefinition.java | 20 +--------------
.../infra/repository/RepositoryFacade.java | 11 +++-----
6 files changed, 9 insertions(+), 76 deletions(-)
diff --git a/structure-infra-starter/README.md b/structure-infra-starter/README.md
index dd2f2fe..64c0e92 100644
--- a/structure-infra-starter/README.md
+++ b/structure-infra-starter/README.md
@@ -122,9 +122,6 @@ RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass);
| `po` | `Class>` | `Object.class` | 持久化对象类 |
| `id` | `Class>` | `Long.class` | 主键类型 |
| `description` | String | `""` | 描述 |
-| `cache` | boolean | `false` | 是否启用缓存 |
-| `cacheTime` | long | `60L` | 缓存 TTL |
-| `cacheTimeUnit` | `TimeUnit` | `SECONDS` | 缓存单位 |
| `cqrs` | boolean | `false` | 是否启用 CQRS |
| `readDelegateClass` | `Class>` | `Object.class` | 读代理类(CQRS 时必填) |
@@ -385,8 +382,6 @@ public class UserEventListener {
|-----|------|-------|------|
| `defaultEventChannel` | `EventChannel` | `SPRING_EVENT` | `DEFAULT` 事件的默认通道 |
| `cqrs` | `Boolean` | `false` | 全局 CQRS 开关(仅作建议,以 `@Repository` 注解为准) |
-| `cacheTime` | `Long` | `60L` | 默认缓存 TTL |
-| `cacheTimeUnit` | `TimeUnit` | `SECONDS` | 默认缓存单位 |
| `schedulePoolSize` | `Integer` | `Runtime.availableProcessors()` | 调度线程池大小 |
| `type` | `RepositoryType` | - | 默认持久化类型(用于触发各 starter 的条件装配) |
diff --git a/structure-infra-starter/pom.xml b/structure-infra-starter/pom.xml
index 5a0e82c..dd85241 100644
--- a/structure-infra-starter/pom.xml
+++ b/structure-infra-starter/pom.xml
@@ -32,10 +32,6 @@
* 标注在 {@link cn.structure.infra.repository.RepositoryFacade} 的子类上,
- * 用于声明一个领域仓储及其元数据(实体类型、PO 类型、主键类型、缓存策略、CQRS 配置等)。
+ * 用于声明一个领域仓储及其元数据(实体类型、PO 类型、主键类型、CQRS 配置等)。
*
* 框架在启动时通过 {@link cn.structure.infra.repository.RepositoryBeanPostProcessor}
* 扫描此注解,并根据配置自动注入对应的 BASE/READ Delegate。
@@ -90,27 +90,6 @@
String description() default "";
- /**
- * 是否启用缓存
- *
- * @return true 表示启用缓存
- */
- boolean cache() default false;
-
- /**
- * 缓存时间
- *
- * @return 缓存过期时间数值
- */
- long cacheTime() default 60L;
-
- /**
- * 缓存时间单位
- *
- * @return 缓存时间单位
- */
- TimeUnit cacheTimeUnit() default TimeUnit.SECONDS;
-
/**
* 是否启用 CQRS 读写分离
*
diff --git a/structure-infra-starter/src/main/java/cn/structure/infra/properties/InfraProperties.java b/structure-infra-starter/src/main/java/cn/structure/infra/properties/InfraProperties.java
index ad06abd..ee31b8b 100644
--- a/structure-infra-starter/src/main/java/cn/structure/infra/properties/InfraProperties.java
+++ b/structure-infra-starter/src/main/java/cn/structure/infra/properties/InfraProperties.java
@@ -5,12 +5,12 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
-import java.util.concurrent.TimeUnit;
+
/**
* 基础设施框架配置属性
*
- * 对应 YAML 配置前缀:{@code structure.infra},集中管理事件、CQRS、缓存、调度等
+ * 对应 YAML 配置前缀:{@code structure.infra},集中管理事件、CQRS、调度等
* 框架级参数。被 {@link cn.structure.infra.configuration.AutoEventConfiguration}、
* {@link cn.structure.infra.configuration.AutoScheduleConfiguration} 等自动装配类引用。
*
@@ -20,8 +20,6 @@
* infra:
* default-event-channel: SPRING_EVENT
* cqrs: false
- * cache-time: 60
- * cache-time-unit: SECONDS
* schedule-pool-size: 8
*
*
@@ -51,20 +49,6 @@ public class InfraProperties {
*/
private Boolean cqrs = false;
- /**
- * 默认缓存时间
- *
- * @return 缓存过期时间数值
- */
- private Long cacheTime = 60L;
-
- /**
- * 默认缓存时间单位
- *
- * @return 缓存时间单位
- */
- private TimeUnit cacheTimeUnit = TimeUnit.SECONDS;
-
/**
* 调度线程池大小,默认 CPU 核心数
*
diff --git a/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryDefinition.java b/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryDefinition.java
index c1d2b80..e2fce1f 100644
--- a/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryDefinition.java
+++ b/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryDefinition.java
@@ -4,7 +4,7 @@
import cn.structure.infra.repository.RepositoryType;
import lombok.Data;
-import java.util.concurrent.TimeUnit;
+
/**
* 仓储定义元数据
@@ -48,21 +48,6 @@ public class RepositoryDefinition {
*/
private String description;
- /**
- * 是否启用缓存
- */
- private boolean cache;
-
- /**
- * 缓存时间
- */
- private long cacheTime;
-
- /**
- * 缓存时间单位
- */
- private TimeUnit cacheTimeUnit;
-
/**
* 是否启用 CQRS 读写分离
*
@@ -101,9 +86,6 @@ public static RepositoryDefinition fromAnnotation(Repository annotation, String
definition.setPoClass(annotation.po());
definition.setIdClass(annotation.id());
definition.setDescription(annotation.description());
- definition.setCache(annotation.cache());
- definition.setCacheTime(annotation.cacheTime());
- definition.setCacheTimeUnit(annotation.cacheTimeUnit());
definition.setCqrs(annotation.cqrs());
definition.setReadDelegateClass(annotation.readDelegateClass());
definition.setAnnotation(annotation);
diff --git a/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryFacade.java b/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryFacade.java
index 308c076..fdf2f99 100644
--- a/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryFacade.java
+++ b/structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryFacade.java
@@ -3,12 +3,12 @@
import cn.structure.common.repository.ICrudRepository;
import cn.structure.common.vo.ReqPage;
import cn.structure.common.vo.ResPage;
-import cn.structured.datascope.cache.manager.DataScopeCacheManager;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
+
import java.util.List;
import java.util.Optional;
@@ -39,10 +39,7 @@
@Slf4j
public class RepositoryFacade
- * 通过反射调用无参构造函数创建 Entity 实例,并使用 {@link BeanUtils#copyProperties}
+ * 通过反射调用无参构造函数创建 Entity 实例,并使用 {@link BeanUtils#copyProperties(Object, Object)}
* 复制同名属性。子类可重写以实现自定义映射逻辑。
*
* @param po 持久化对象,为 null 时返回 null
@@ -377,7 +374,7 @@ protected T toEntity(P po) {
/**
* Entity → PO 转换
*
- * 通过反射调用无参构造函数创建 PO 实例,并使用 {@link BeanUtils#copyProperties}
+ * 通过反射调用无参构造函数创建 PO 实例,并使用 {@link BeanUtils#copyProperties(Object, Object)}
* 复制同名属性。子类可重写以实现自定义映射逻辑。
*
* @param entity 领域实体,为 null 时返回 null
From 7b8f9b3458b1b56f8d5314c4cd014f5907081163 Mon Sep 17 00:00:00 2001
From: chuck <361648887@qq.com>
Date: Fri, 17 Jul 2026 15:20:34 +0800
Subject: [PATCH 2/9] =?UTF-8?q?feat(stream):=20=E6=B7=BB=E5=8A=A0=E5=8A=A8?=
=?UTF-8?q?=E6=80=81=E6=B3=A8=E5=86=8CSpring=20Cloud=20Stream=20binding?=
=?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在DefaultStreamEventManagerImpl中注入ConfigurableEnvironment用于动态配置注册
- 新增registerCloudStreamBinding方法将binding配置注册到Spring环境确保StreamBridge能正确找到目标
- 为输入输出绑定自动生成对应的destination、content-type、group和并发配置
- 使用LinkedHashMap维护配置顺序并通过MapPropertySource动态添加到环境最前面
- 更新StreamAutoConfiguration构造方法以传入环境对象
- 修改测试代码适配新的构造函数参数
- 将版本号从1.1.0-SNAPSHOT升级到1.1-SNAPSHOT
---
pom.xml | 2 +-
.../consumer/StreamMessageConsumer.java | 2 +-
.../sample/stream/StreamEventManagerTest.java | 4 +-
.../StreamAutoConfiguration.java | 7 +-
.../DefaultStreamEventManagerImpl.java | 81 ++++++++++++++++++-
5 files changed, 90 insertions(+), 6 deletions(-)
diff --git a/pom.xml b/pom.xml
index fdca93a..4a5d2ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
实现说明:使用 synchronized 保证并发注册的幂等性,已存在时仅打印告警并返回。
* contentType/concurrency 为 null 时分别回退到全局默认值。
+ * 同时注册 Spring Cloud Stream binding 配置到 Environment,确保 StreamBridge 能找到正确的 destination。
*/
@Override
public void registerBinding(String bindingName, String destination, String group, String contentType, Integer concurrency) {
@@ -311,11 +331,70 @@ public void registerBinding(String bindingName, String destination, String group
streamProperties.getBindings().put(bindingName, binding);
+ // 注册 Spring Cloud Stream binding 配置到 Environment
+ registerCloudStreamBinding(bindingName, destination, group, binding.getContentType(), binding.getConcurrency());
+
log.info("Dynamically registered binding: {}, destination: {}, group: {}, contentType: {}",
bindingName, destination, group, binding.getContentType());
}
}
+ /**
+ * 将 binding 配置注册到 Spring Cloud Stream 的 Environment,确保 StreamBridge 发送消息时能找到正确的 destination。
+ *
+ * @param bindingName 绑定名称
+ * @param destination 目标 destination(exchange/topic)
+ * @param group 消费者组
+ * @param contentType 内容类型
+ * @param concurrency 消费并发数
+ */
+ private void registerCloudStreamBinding(String bindingName, String destination, String group,
+ String contentType, Integer concurrency) {
+ String inputBinding = bindingName + "-in-0";
+ String outputBinding = bindingName + "-out-0";
+
+ String inputDestKey = SPRING_BINDINGS_PREFIX + "." + inputBinding + ".destination";
+
+ // 检查是否已存在配置(避免覆盖已有的显式配置)
+ if (environment.containsProperty(inputDestKey)) {
+ log.debug("Cloud Stream binding already configured: {}", inputDestKey);
+ return;
+ }
+
+ Map 实现说明:
*
*
*/
@Override
public {bindingName}-out-0 输出通道
- * 当检测到 Elasticsearch 相关依赖({@link org.springframework.data.elasticsearch.core.ElasticsearchOperations})时自动配置, - * 注册 Elasticsearch 文档操作所需的核心组件,使其与仓储框架无缝集成。 - *
- * 注册的 Bean: - *
- * 工作机制: - *
- * 配置方式: - *
- * 额外配置: - *
- * 负责根据 PO 类创建 ElasticsearchRepositoryDelegate 实例,通过 ElasticsearchOperations 进行文档操作。 - * 当 RepositoryFacade 需要获取 Elasticsearch 类型的 RepositoryDelegate 时,会通过此工厂进行创建。 - * - * @param elasticsearchOperations Elasticsearch 操作模板,用于执行索引、查询等操作 - * @return ElasticsearchDelegateFactory 实例 - */ - @Bean - @ConditionalOnBean(ElasticsearchOperations.class) - public ElasticsearchDelegateFactory elasticsearchDelegateFactory(ElasticsearchOperations elasticsearchOperations) { - return new ElasticsearchDelegateFactory(elasticsearchOperations); - } - - /** - * 创建 Elasticsearch 委托 Bean 后处理器 - *
- * 在 Bean 初始化完成后,自动为 ElasticsearchRepositoryDelegate 实现类注入 ElasticsearchOperations 和实体类。 - *
- * 处理逻辑: - * 1. 扫描所有 Bean,筛选出 ElasticsearchRepositoryDelegate 的实例 - * 2. 从 Spring 上下文获取 ElasticsearchOperations 并注入到 Delegate 实例中 - * 3. 检查是否存在 {@link cn.structure.infra.annotations.DelegateFor} 注解 - * 4. 将注解中指定的 PO 类设置到 Delegate 实例中 - * - * @return ElasticsearchDelegateBeanPostProcessor 实例 - */ - @Bean - @ConditionalOnBean(ElasticsearchOperations.class) - public ElasticsearchDelegateBeanPostProcessor elasticsearchDelegateBeanPostProcessor() { - return new ElasticsearchDelegateBeanPostProcessor(); - } } diff --git a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateBeanPostProcessor.java b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateBeanPostProcessor.java deleted file mode 100644 index 23c27b0..0000000 --- a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateBeanPostProcessor.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.structure.infra.elasticsearch.repository; - -import cn.structure.infra.annotations.DelegateFor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; - -/** - * Elasticsearch RepositoryDelegate 的 BeanPostProcessor,负责为用户自定义 Delegate 子类自动注入 ElasticsearchOperations 与实体类型。 - *
- * 在仓储框架中,业务方可继承 {@link ElasticsearchRepositoryDelegate} 实现自定义 Delegate,并通过 - * {@link DelegateFor} 注解声明其服务的 PO 类型。本后处理器在 Bean 初始化完成后: - *
- * 与 {@link ElasticsearchDelegateFactory} 的分工:工厂负责"无自定义 Delegate 时自动创建", - * 本处理器负责"已有自定义 Delegate 时补齐依赖",二者协同保证 RepositoryFacade 总能拿到可用的 Delegate。 - * - * @author chuck - * @version 1.0.1 - * @since 2026/6/28 - */ -@Slf4j -public class ElasticsearchDelegateBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware { - - /** Spring 上下文,用于按类型获取 ElasticsearchOperations */ - private ApplicationContext applicationContext; - - /** - * 注入 Spring 应用上下文,供后续按类型查询 Bean。 - * - * @param applicationContext Spring 应用上下文 - * @throws BeansException 上下文注入异常 - */ - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - /** - * 在 Bean 初始化完成后,对自定义 ElasticsearchRepositoryDelegate 实现类注入 ElasticsearchOperations 与实体类型。 - *
- * 仅当 Bean 是 {@link ElasticsearchRepositoryDelegate} 实例时执行注入; - * ElasticsearchOperations 解析失败仅告警不抛异常。 - * - * @param bean 待处理的 Bean 实例 - * @param beanName Bean 名称 - * @return 原始 Bean(已注入依赖),未匹配类型时原样返回 - * @throws BeansException 处理过程中的异常 - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ElasticsearchRepositoryDelegate) { - ElasticsearchRepositoryDelegate delegate = (ElasticsearchRepositoryDelegate) bean; - try { - // 按类型从容器获取 ElasticsearchOperations 并注入 - ElasticsearchOperations elasticsearchOperations = applicationContext.getBean(ElasticsearchOperations.class); - delegate.setElasticsearchOperations(elasticsearchOperations); - - // 读取 @DelegateFor 注解,识别该 Delegate 服务的 PO 类型并注入 - DelegateFor annotation = bean.getClass().getAnnotation(DelegateFor.class); - if (annotation != null && annotation.po() != void.class) { - delegate.setEntityClass(annotation.po()); - } - - log.info("Injected ElasticsearchOperations into ElasticsearchRepositoryDelegate: {}", beanName); - } catch (Exception e) { - log.warn("Failed to inject ElasticsearchOperations into ElasticsearchRepositoryDelegate {}: {}", beanName, e.getMessage()); - } - } - return bean; - } -} \ No newline at end of file diff --git a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateFactory.java b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateFactory.java deleted file mode 100644 index d855024..0000000 --- a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.structure.infra.elasticsearch.repository; - -import cn.structure.infra.repository.RepositoryDelegate; -import cn.structure.infra.repository.RepositoryDelegateFactory; -import cn.structure.infra.repository.RepositoryType; -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; - -/** - * Elasticsearch 仓储委托工厂 - *
- * 实现 {@link RepositoryDelegateFactory} SPI,自动创建 {@link ElasticsearchRepositoryDelegate} 实例。 - * 在仓储框架中,当 {@code RepositoryFacade} 找不到用户自定义的 Delegate 时,会通过本工厂按 PO 类型 - * 创建默认 Delegate 实例(依赖容器中的 {@link ElasticsearchOperations})。 - *
- * 与 {@link ElasticsearchDelegateBeanPostProcessor} 的分工:本工厂负责"无自定义 Delegate 时创建默认实现", - * BeanPostProcessor 负责"已有自定义子类时补齐依赖"。 - * - * @author chuck - * @version 1.0.1 - * @since 2026/6/28 - */ -public class ElasticsearchDelegateFactory implements RepositoryDelegateFactory { - - /** Elasticsearch 操作模板,由容器注入并共享给所有 Delegate 实例 */ - private final ElasticsearchOperations elasticsearchOperations; - - /** - * 构造工厂,注入 ElasticsearchOperations。 - * - * @param elasticsearchOperations Elasticsearch 操作模板 - */ - public ElasticsearchDelegateFactory(ElasticsearchOperations elasticsearchOperations) { - this.elasticsearchOperations = elasticsearchOperations; - } - - /** - * 返回该工厂支持的仓储类型,用于 SPI 路由匹配。 - * - * @return 固定返回 {@link RepositoryType#ELASTICSEARCH} - */ - @Override - public RepositoryType getType() { - return RepositoryType.ELASTICSEARCH; - } - - /** - * 为指定 PO 类型创建 {@link ElasticsearchRepositoryDelegate} 实例。 - *
- * Elasticsearch 实现不依赖 Mapper 查找,直接以入参 PO 类型构造 Delegate,因此失败概率较低; - * 出现异常时返回 null,由上层 RepositoryFacade 继续尝试其他工厂或抛出异常。 - * - * @param poClass PO 实体类型 - * @param idClass 主键类型(当前实现未使用,保留以匹配 SPI 签名) - * @return 已注入 ElasticsearchOperations 的 Delegate 实例;构造异常时返回 null - */ - @Override - @SuppressWarnings({"unchecked", "rawtypes"}) - public RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass) { - try { - return new ElasticsearchRepositoryDelegate(elasticsearchOperations, poClass); - } catch (Exception e) { - return null; - } - } -} diff --git a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java index 2bfe80c..53d7d7a 100644 --- a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java +++ b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java @@ -2,8 +2,11 @@ import cn.structure.common.vo.ReqPage; import cn.structure.common.vo.ResPage; +import cn.structure.infra.repository.GenericTypeResolver; import cn.structure.infra.repository.RepositoryDelegate; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; @@ -12,6 +15,7 @@ import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.Query; +import jakarta.persistence.Id; import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; @@ -33,229 +37,164 @@ * 实现说明: *
持久化对象类型(ES Document)
* @param
- * 创建后由 {@link ElasticsearchDelegateBeanPostProcessor} 通过 setter 注入依赖。
- */
+ protected Class poClass;
+ protected Class ) GenericTypeResolver.resolvePoClass(getClass());
+ this.idClass = (Class
- * 委托给 {@link ElasticsearchOperations#save(Object)},由 ES 依据 _id 自动判断新增或覆盖索引。
- *
- * @param entity 实体对象,为 null 时返回 null
- * @return 保存后的实体(与入参同一引用)
- */
@Override
- public T save(T entity) {
+ public E save(E entity) {
if (entity == null) {
return null;
}
- T saved = elasticsearchOperations.save(entity);
- log.debug("Saved entity: {}", saved);
- return saved;
+ P po = toPo(entity);
+ P savedPo = elasticsearchOperations.save(po);
+ log.debug("Saved entity: {}", savedPo);
+ return toEntity(savedPo);
}
- /**
- * 根据主键删除文档。
- *
- * ES 文档 ID 必须为字符串,主键值通过 {@code String.valueOf(id)} 转换后再删除。
- *
- * @param id 主键值,为 null 时不执行任何操作
- */
@Override
public void removeById(ID id) {
if (id != null) {
- // ID 统一转换为 String 作为 ES 文档 ID
- elasticsearchOperations.delete(String.valueOf(id), entityClass);
+ elasticsearchOperations.delete(String.valueOf(id), poClass);
log.debug("Removed entity: id={}", id);
}
}
- /**
- * 根据主键查询文档。
- *
- * ES 文档 ID 必须为字符串,主键值通过 {@code String.valueOf(id)} 转换后再查询。
- *
- * @param id 主键值,为 null 时返回 null
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T findById(ID id) {
+ public E findById(ID id) {
if (id == null) {
return null;
}
- // ID 统一转换为 String 作为 ES 文档 ID
- T entity = elasticsearchOperations.get(String.valueOf(id), entityClass);
- log.debug("Find by id: id={}, found={}", id, entity != null);
- return entity;
+ P po = elasticsearchOperations.get(String.valueOf(id), poClass);
+ log.debug("Find by id: id={}, found={}", id, po != null);
+ return toEntity(po);
}
- /**
- * 根据主键查询(与 findById 等价,语义上用于"读模型",常作为 CQRS 读侧)。
- *
- * @param id 主键值
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T queryById(ID id) {
+ public E queryById(ID id) {
return findById(id);
}
- /**
- * 根据主键查询并以 {@link Optional} 包装返回。
- *
- * @param id 主键值
- * @return 包含实体的 Optional,未找到时为 {@link Optional#empty()}
- */
@Override
- public Optional
- * 通过反射构建 {@link CriteriaQuery},取首条 SearchHit 的 content;无匹配时返回 null。
- *
- * @param condition 查询条件对象,为 null 时返回 null
- * @return 首条匹配记录,无匹配时返回 null
- */
@Override
- public T queryOne(T condition) {
+ public E queryOne(E condition) {
if (condition == null) {
return null;
}
- Query query = buildQuery(condition);
- SearchHits searchHits = elasticsearchOperations.search(query, poClass);
+ P po = searchHits.hasSearchHits() ? searchHits.getSearchHit(0).getContent() : null;
+ return toEntity(po);
}
- /**
- * 根据条件查询单条记录,并以 {@link Optional} 包装返回。
- *
- * @param condition 查询条件对象
- * @return 包含首条匹配记录的 Optional
- */
@Override
- public Optional
- * 条件为 null 时使用 {@code Criteria.where("*").exists()} 匹配全部文档;
- * 否则按非空字段构建等值 Criteria。
- *
- * @param condition 查询条件对象,可为 null
- * @return 匹配的实体列表,无匹配时返回空列表
- */
@Override
- public List searchHits = elasticsearchOperations.search(query, poClass);
return searchHits.getSearchHits().stream()
- .map(hit -> hit.getContent())
+ .map(hit -> toEntity(hit.getContent()))
.collect(Collectors.toList());
}
- Query query = buildQuery(condition);
- SearchHits searchHits = elasticsearchOperations.search(query, poClass);
return searchHits.getSearchHits().stream()
- .map(hit -> hit.getContent())
+ .map(hit -> toEntity(hit.getContent()))
.collect(Collectors.toList());
}
- /**
- * 分页查询。
- *
- * 通过 {@code Criteria.where("*").exists()} 匹配全部文档,叠加 {@link PageRequest} 分页参数,
- * 由 ES 原生分页(from/size)执行。
- *
- * @param reqPage 分页请求(页码从 1 开始、每页大小,为 null 时取默认 1/10)
- * @return 分页结果,含当前页、总页数、总条数、当前页记录
- */
@Override
- public ResPage searchHits = elasticsearchOperations.search(query, poClass);
- ResPage
- * 当检测到 JPA 相关依赖({@link org.springframework.data.jpa.repository.JpaRepository})时自动配置,
- * 注册 JPA 持久化所需的核心组件,使其与仓储框架无缝集成。
- *
- * 注册的 Bean:
- *
- * 工作机制:
- *
- * 额外配置:
- *
- * 负责根据 PO 类创建 JpaRepositoryDelegate 实例,通过 EntityManager 进行持久化操作。
- * 当 RepositoryFacade 需要获取 JPA 类型的 RepositoryDelegate 时,会通过此工厂进行创建。
- *
- * @param entityManager JPA 实体管理器,用于执行数据库操作
- * @return JpaDelegateFactory 实例
- */
- @Bean
- @ConditionalOnBean(EntityManager.class)
- public JpaDelegateFactory jpaDelegateFactory(EntityManager entityManager) {
- return new JpaDelegateFactory(entityManager);
- }
-
- /**
- * 创建 JPA 委托 Bean 后处理器
- *
- * 在 Bean 初始化完成后,自动为 JpaRepositoryDelegate 实现类注入 EntityManager 和实体类。
- *
- * 处理逻辑:
- * 1. 扫描所有 Bean,筛选出 JpaRepositoryDelegate 的实例
- * 2. 从 Spring 上下文获取 EntityManager 并注入到 Delegate 实例中
- * 3. 检查是否存在 {@link cn.structure.infra.annotations.DelegateFor} 注解
- * 4. 将注解中指定的 PO 类设置到 Delegate 实例中
- *
- * @return JpaDelegateBeanPostProcessor 实例
- */
- @Bean
- @ConditionalOnClass(name = "jakarta.persistence.EntityManager")
- public JpaDelegateBeanPostProcessor jpaDelegateBeanPostProcessor() {
- return new JpaDelegateBeanPostProcessor();
- }
}
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateBeanPostProcessor.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateBeanPostProcessor.java
deleted file mode 100644
index 95d4871..0000000
--- a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateBeanPostProcessor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package cn.structure.infra.jpa.repository;
-
-import cn.structure.infra.annotations.DelegateFor;
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-
-/**
- * JPA RepositoryDelegate 的 BeanPostProcessor,负责为用户自定义 Delegate 子类自动注入 EntityManager 与实体类型。
- *
- * 在仓储框架中,业务方可继承 {@link JpaRepositoryDelegate} 实现自定义 Delegate,并通过
- * {@link DelegateFor} 注解声明其服务的 PO 类型。本后处理器在 Bean 初始化完成后:
- *
- * 与 {@link JpaDelegateFactory} 的分工:工厂负责"无自定义 Delegate 时自动创建",
- * 本处理器负责"已有自定义 Delegate 时补齐依赖",二者协同保证 RepositoryFacade 总能拿到可用的 Delegate。
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
-@Slf4j
-public class JpaDelegateBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
-
- /** Spring 上下文,用于解析 EntityManager */
- private ApplicationContext applicationContext;
-
- /**
- * 注入 Spring 应用上下文,供后续按类型/名称查询 Bean。
- *
- * @param applicationContext Spring 应用上下文
- * @throws BeansException 上下文注入异常
- */
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
-
- /**
- * 在 Bean 初始化完成后,对自定义 JpaRepositoryDelegate 实现类注入 EntityManager 与实体类型。
- *
- * 仅当 Bean 是 {@link JpaRepositoryDelegate} 实例时执行注入;EntityManager 解析失败仅告警不抛异常。
- *
- * @param bean 待处理的 Bean 实例
- * @param beanName Bean 名称
- * @return 原始 Bean(已注入依赖),未匹配类型时原样返回
- * @throws BeansException 处理过程中的异常
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof JpaRepositoryDelegate) {
- JpaRepositoryDelegate delegate = (JpaRepositoryDelegate) bean;
-
- // 解析 EntityManager:by name → by type → create
- EntityManager entityManager = getEntityManager();
- if (entityManager != null) {
- delegate.setEntityManager(entityManager);
- log.info("Injected EntityManager into JpaRepositoryDelegate: {}", beanName);
- } else {
- log.warn("No EntityManager available to inject into JpaRepositoryDelegate: {}", beanName);
- }
-
- // 读取 @DelegateFor 注解,识别该 Delegate 服务的 PO 类型并注入
- DelegateFor annotation = bean.getClass().getAnnotation(DelegateFor.class);
- if (annotation != null && annotation.po() != void.class) {
- delegate.setEntityClass(annotation.po());
- log.info("Injected entityClass {} into JpaRepositoryDelegate: {}", annotation.po().getSimpleName(), beanName);
- }
- }
- return bean;
- }
-
- /**
- * 解析 {@link EntityManager} 实例。
- *
- * 解析顺序(按优先级):
- *
- * 实现 {@link RepositoryDelegateFactory} SPI,自动创建 {@link JpaRepositoryDelegate} 实例。
- * 在仓储框架中,当 {@code RepositoryFacade} 找不到用户自定义的 Delegate 时,会通过本工厂按 PO 类型
- * 创建默认 Delegate 实例(依赖容器中的 {@link EntityManager})。
- *
- * 与 {@link JpaDelegateBeanPostProcessor} 的分工:本工厂负责"无自定义 Delegate 时创建默认实现",
- * BeanPostProcessor 负责"已有自定义子类时补齐依赖"。
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
-public class JpaDelegateFactory implements RepositoryDelegateFactory {
-
- /** JPA 实体管理器,由容器注入并共享给所有 Delegate 实例 */
- private final EntityManager entityManager;
-
- /**
- * 构造工厂,注入 EntityManager。
- *
- * @param entityManager JPA 实体管理器,用于执行持久化操作
- */
- public JpaDelegateFactory(EntityManager entityManager) {
- this.entityManager = entityManager;
- }
-
- /**
- * 返回该工厂支持的仓储类型,用于 SPI 路由匹配。
- *
- * @return 固定返回 {@link RepositoryType#JPA}
- */
- @Override
- public RepositoryType getType() {
- return RepositoryType.JPA;
- }
-
- /**
- * 为指定 PO 类型创建 {@link JpaRepositoryDelegate} 实例。
- *
- * JPA 实现不依赖 Mapper 查找,直接以入参 PO 类型构造 Delegate,因此失败概率较低;
- * 出现异常时返回 null,由上层 RepositoryFacade 继续尝试其他工厂或抛出异常。
- *
- * @param poClass PO 实体类型
- * @param idClass 主键类型(当前实现未使用,保留以匹配 SPI 签名)
- * @return 已注入 EntityManager 的 Delegate 实例;构造异常时返回 null
- */
- @Override
- @SuppressWarnings({"unchecked", "rawtypes"})
- public RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass) {
- try {
- return new JpaRepositoryDelegate(entityManager, poClass);
- } catch (Exception e) {
- return null;
- }
- }
-}
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java
index 6ce7983..168399b 100644
--- a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java
+++ b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java
@@ -2,11 +2,11 @@
import cn.structure.common.vo.ReqPage;
import cn.structure.common.vo.ResPage;
+import cn.structure.infra.repository.GenericTypeResolver;
import cn.structure.infra.repository.RepositoryDelegate;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.PageRequest;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
@@ -14,10 +14,12 @@
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
+import jakarta.persistence.Id;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* 基于 JPA 的 RepositoryDelegate 适配实现
@@ -36,259 +38,234 @@
* 持久化对象类型(JPA Entity)
* @param
- * 创建后由 {@link JpaDelegateBeanPostProcessor} 通过 setter 注入依赖。
- */
+ protected Class poClass;
+ protected Class ) GenericTypeResolver.resolvePoClass(getClass());
+ this.idClass = (Class
- * 委托给 {@link EntityManager#merge(Object)},由 JPA 根据实体主键自动判断新增或更新。
- *
- * @param entity 实体对象,为 null 或依赖未就绪时返回 null
- * @return merge 后的实体实例(可能是新对象引用)
- */
@Override
- public T save(T entity) {
- if (entity == null || entityManager == null || entityClass == null) {
+ public String getIdFieldName() {
+ return idFieldName;
+ }
+
+ @Override
+ public E save(E entity) {
+ if (entity == null || entityManager == null || poClass == null) {
return null;
}
- T saved = entityManager.merge(entity);
- log.debug("Saved entity: {}", saved);
- return saved;
+ P po = toPo(entity);
+ P savedPo = entityManager.merge(po);
+ log.debug("Saved entity: {}", savedPo);
+ return toEntity(savedPo);
}
- /**
- * 根据主键删除记录。
- *
- * JPA 删除前必须先 find 出受管实体再 remove,无法直接按 ID 删除。
- *
- * @param id 主键值,为 null 时不执行任何操作
- */
@Override
public void removeById(ID id) {
- if (id != null) {
- // JPA 删除需先加载受管实体再 remove
- T entity = findById(id);
- if (entity != null) {
- entityManager.remove(entity);
+ if (id != null && entityManager != null && poClass != null) {
+ P po = entityManager.find(poClass, id);
+ if (po != null) {
+ entityManager.remove(po);
log.debug("Removed entity: id={}", id);
}
}
}
- /**
- * 根据主键查询实体。
- *
- * @param id 主键值,为 null 时返回 null
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T findById(ID id) {
- if (id == null) {
+ public E findById(ID id) {
+ if (id == null || entityManager == null || poClass == null) {
return null;
}
- T entity = entityManager.find(entityClass, id);
- log.debug("Find by id: id={}, found={}", id, entity != null);
- return entity;
+ P po = entityManager.find(poClass, id);
+ log.debug("Find by id: id={}, found={}", id, po != null);
+ return toEntity(po);
}
- /**
- * 根据主键查询(与 findById 等价,语义上用于"读模型")。
- *
- * @param id 主键值
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T queryById(ID id) {
+ public E queryById(ID id) {
return findById(id);
}
- /**
- * 根据主键查询并以 {@link Optional} 包装返回。
- *
- * @param id 主键值
- * @return 包含实体的 Optional,未找到时为 {@link Optional#empty()}
- */
@Override
- public Optional
- * 通过 Criteria API 构建等值条件,取结果集首条;多于一条时仅返回首条。
- *
- * @param condition 查询条件对象,为 null 时返回 null
- * @return 首条匹配记录,无匹配时返回 null
- */
@Override
- public T queryOne(T condition) {
+ public E queryOne(E condition) {
if (condition == null) {
return null;
}
- List
- * 条件为 null 时查询全部;否则按非空字段构建 Criteria 等值条件。
- *
- * @param condition 查询条件对象,可为 null
- * @return 匹配的实体列表,无匹配时返回空列表
- */
@Override
- public List
- * 注意:JPA 不支持原生分页时使用内存分页——先 findAll 取全量结果,
- * 再按 subList 切片返回当前页。该实现适用于中小数据量;大数据量场景
- * 建议用户自定义 Delegate 子类覆盖本方法,使用原生 SQL 分页。
- *
- * @param reqPage 分页请求(页码从 1 开始、每页大小,为 null 时取默认 1/10)
- * @return 分页结果,含当前页、总页数、总条数、当前页记录
- */
@Override
- public ResPage query = cb.createQuery(poClass);
+ query.from(poClass);
- ResPage typedQuery = entityManager.createQuery(query);
+ typedQuery.setFirstResult(pageNum * pageSize);
+ typedQuery.setMaxResults(pageSize);
+
+ pageContent = typedQuery.getResultList().stream()
+ .map(this::toEntity)
+ .collect(Collectors.toList());
+ } else {
+ pageContent = List.of();
+ }
+
+ ResPage query = cb.createQuery(poClass);
+ query.from(poClass);
+ return entityManager.createQuery(query).getResultList().stream()
+ .map(this::toEntity)
+ .collect(Collectors.toList());
}
- /**
- * 通过 Criteria API 按条件等值查询。
- *
- * @param condition 条件对象
- * @return 匹配的实体列表
- */
- private List query = cb.createQuery(poClass);
+ Root root = query.from(poClass);
- // 构建等值 Predicate 数组并拼接到 WHERE 子句
Predicate[] predicates = buildPredicates(cb, root, condition);
if (predicates.length > 0) {
query.where(predicates);
}
- return entityManager.createQuery(query).getResultList();
+ return entityManager.createQuery(query).getResultList().stream()
+ .map(this::toEntity)
+ .collect(Collectors.toList());
}
- /**
- * 反射读取条件对象非空字段,构建等值 {@link Predicate} 数组。
- *
- * @param cb CriteriaBuilder
- * @param root 查询根
- * @param condition 条件对象
- * @return 等值 Predicate 数组
- */
- private Predicate[] buildPredicates(CriteriaBuilder cb, Root root, E condition) {
List query = cb.createQuery(poClass);
+ Root root = query.from(poClass);
+
+ query.where(root.get(idFieldName).in(ids));
+
+ return entityManager.createQuery(query).getResultList().stream()
+ .map(this::toEntity)
+ .collect(Collectors.toList());
}
- /**
- * 按条件统计记录数。
- *
- * 当前实现通过查询结果列表的 size 计数(未走 COUNT 查询),适用于中小数据量。
- *
- * @param condition 条件对象,为 null 时统计全表
- * @return 匹配的记录数
- */
@Override
- public long count(T condition) {
+ public long count(E condition) {
+ if (entityManager == null || poClass == null) {
+ return 0;
+ }
+
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+
if (condition == null) {
- return findAll().size();
+ return executeCountQuery(cb);
}
- return queryList(condition).size();
+
+ CriteriaQuery root = countQuery.from(poClass);
+ countQuery.select(cb.count(root));
+
+ Predicate[] predicates = buildPredicates(cb, root, condition);
+ if (predicates.length > 0) {
+ countQuery.where(predicates);
+ }
+
+ return entityManager.createQuery(countQuery).getSingleResult();
}
- /**
- * 判断是否存在匹配条件的记录。
- *
- * @param condition 条件对象
- * @return 存在返回 true,否则 false
- */
@Override
- public boolean exists(T condition) {
+ public boolean exists(E condition) {
return count(condition) > 0;
}
+
+ protected E toEntity(P po) {
+ if (po == null) {
+ return null;
+ }
+ if (entityClass == null) {
+ return (E) po;
+ }
+ try {
+ E entity = entityClass.getDeclaredConstructor().newInstance();
+ BeanUtils.copyProperties(po, entity);
+ return entity;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to convert PO to entity", e);
+ }
+ }
+
+ protected P toPo(E entity) {
+ if (entity == null) {
+ return null;
+ }
+ if (poClass == null) {
+ return (P) entity;
+ }
+ try {
+ P po = poClass.getDeclaredConstructor().newInstance();
+ BeanUtils.copyProperties(entity, po);
+ return po;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to convert entity to PO", e);
+ }
+ }
}
\ No newline at end of file
diff --git a/structure-infra-mongodb-starter/pom.xml b/structure-infra-mongodb-starter/pom.xml
index ba7785e..789d10e 100644
--- a/structure-infra-mongodb-starter/pom.xml
+++ b/structure-infra-mongodb-starter/pom.xml
@@ -24,6 +24,10 @@
- * 当检测到 MongoDB 相关依赖({@link org.springframework.data.mongodb.core.MongoTemplate})时自动配置,
- * 注册 MongoDB 文档操作所需的核心组件,使其与仓储框架无缝集成。
- *
- * 注册的 Bean:
- *
- * 工作机制:
- *
- * 配置方式:
- *
- * 额外配置:
- *
- * 负责根据 PO 类创建 MongoRepositoryDelegate 实例,通过 MongoTemplate 进行文档操作。
- * 当 RepositoryFacade 需要获取 MongoDB 类型的 RepositoryDelegate 时,会通过此工厂进行创建。
- *
- * @param mongoTemplate MongoDB 操作模板,用于执行增删改查等操作
- * @return MongoDelegateFactory 实例
- */
- @Bean
- @ConditionalOnBean(MongoTemplate.class)
- public MongoDelegateFactory mongoDelegateFactory(MongoTemplate mongoTemplate) {
- return new MongoDelegateFactory(mongoTemplate);
- }
-
- /**
- * 创建 MongoDB 委托 Bean 后处理器
- *
- * 在 Bean 初始化完成后,自动为 MongoRepositoryDelegate 实现类注入 MongoTemplate 和实体类。
- *
- * 处理逻辑:
- * 1. 扫描所有 Bean,筛选出 MongoRepositoryDelegate 的实例
- * 2. 从 Spring 上下文获取 MongoTemplate 并注入到 Delegate 实例中
- * 3. 检查是否存在 {@link cn.structure.infra.annotations.DelegateFor} 注解
- * 4. 将注解中指定的 PO 类设置到 Delegate 实例中
- *
- * @return MongoDelegateBeanPostProcessor 实例
- */
- @Bean
- @ConditionalOnBean(MongoTemplate.class)
- public MongoDelegateBeanPostProcessor mongoDelegateBeanPostProcessor() {
- return new MongoDelegateBeanPostProcessor();
- }
}
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoDelegateBeanPostProcessor.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoDelegateBeanPostProcessor.java
deleted file mode 100644
index f71088f..0000000
--- a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoDelegateBeanPostProcessor.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package cn.structure.infra.mongodb.repository;
-
-import cn.structure.infra.annotations.DelegateFor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.data.mongodb.core.MongoTemplate;
-
-/**
- * MongoDB RepositoryDelegate 的 BeanPostProcessor,负责为用户自定义 Delegate 子类自动注入 MongoTemplate 与实体类型。
- *
- * 在仓储框架中,业务方可继承 {@link MongoRepositoryDelegate} 实现自定义 Delegate,并通过
- * {@link DelegateFor} 注解声明其服务的 PO 类型。本后处理器在 Bean 初始化完成后:
- *
- * 与 {@link MongoDelegateFactory} 的分工:工厂负责"无自定义 Delegate 时自动创建",
- * 本处理器负责"已有自定义 Delegate 时补齐依赖",二者协同保证 RepositoryFacade 总能拿到可用的 Delegate。
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
-@Slf4j
-public class MongoDelegateBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
-
- /** Spring 上下文,用于按类型获取 MongoTemplate */
- private ApplicationContext applicationContext;
-
- /**
- * 注入 Spring 应用上下文,供后续按类型查询 Bean。
- *
- * @param applicationContext Spring 应用上下文
- * @throws BeansException 上下文注入异常
- */
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
-
- /**
- * 在 Bean 初始化完成后,对自定义 MongoRepositoryDelegate 实现类注入 MongoTemplate 与实体类型。
- *
- * 仅当 Bean 是 {@link MongoRepositoryDelegate} 实例时执行注入;MongoTemplate 解析失败仅告警不抛异常。
- *
- * @param bean 待处理的 Bean 实例
- * @param beanName Bean 名称
- * @return 原始 Bean(已注入依赖),未匹配类型时原样返回
- * @throws BeansException 处理过程中的异常
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof MongoRepositoryDelegate) {
- MongoRepositoryDelegate delegate = (MongoRepositoryDelegate) bean;
- try {
- // 按类型从容器获取 MongoTemplate 并注入
- MongoTemplate mongoTemplate = applicationContext.getBean(MongoTemplate.class);
- delegate.setMongoTemplate(mongoTemplate);
-
- // 读取 @DelegateFor 注解,识别该 Delegate 服务的 PO 类型并注入
- DelegateFor annotation = bean.getClass().getAnnotation(DelegateFor.class);
- if (annotation != null && annotation.po() != void.class) {
- delegate.setEntityClass(annotation.po());
- }
-
- log.info("Injected MongoTemplate into MongoRepositoryDelegate: {}", beanName);
- } catch (Exception e) {
- log.warn("Failed to inject MongoTemplate into MongoRepositoryDelegate {}: {}", beanName, e.getMessage());
- }
- }
- return bean;
- }
-}
\ No newline at end of file
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoDelegateFactory.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoDelegateFactory.java
deleted file mode 100644
index d7cb15a..0000000
--- a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoDelegateFactory.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package cn.structure.infra.mongodb.repository;
-
-import cn.structure.infra.repository.RepositoryDelegate;
-import cn.structure.infra.repository.RepositoryDelegateFactory;
-import cn.structure.infra.repository.RepositoryType;
-import org.springframework.data.mongodb.core.MongoTemplate;
-
-/**
- * MongoDB 仓储委托工厂
- *
- * 实现 {@link RepositoryDelegateFactory} SPI,自动创建 {@link MongoRepositoryDelegate} 实例。
- * 在仓储框架中,当 {@code RepositoryFacade} 找不到用户自定义的 Delegate 时,会通过本工厂按 PO 类型
- * 创建默认 Delegate 实例(依赖容器中的 {@link MongoTemplate})。
- *
- * 与 {@link MongoDelegateBeanPostProcessor} 的分工:本工厂负责"无自定义 Delegate 时创建默认实现",
- * BeanPostProcessor 负责"已有自定义子类时补齐依赖"。
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
-public class MongoDelegateFactory implements RepositoryDelegateFactory {
-
- /** MongoDB 操作模板,由容器注入并共享给所有 Delegate 实例 */
- private final MongoTemplate mongoTemplate;
-
- /**
- * 构造工厂,注入 MongoTemplate。
- *
- * @param mongoTemplate MongoDB 操作模板
- */
- public MongoDelegateFactory(MongoTemplate mongoTemplate) {
- this.mongoTemplate = mongoTemplate;
- }
-
- /**
- * 返回该工厂支持的仓储类型,用于 SPI 路由匹配。
- *
- * @return 固定返回 {@link RepositoryType#MONGODB}
- */
- @Override
- public RepositoryType getType() {
- return RepositoryType.MONGODB;
- }
-
- /**
- * 为指定 PO 类型创建 {@link MongoRepositoryDelegate} 实例。
- *
- * MongoDB 实现不依赖 Mapper 查找,直接以入参 PO 类型构造 Delegate,因此失败概率较低;
- * 出现异常时返回 null,由上层 RepositoryFacade 继续尝试其他工厂或抛出异常。
- *
- * @param poClass PO 实体类型
- * @param idClass 主键类型(当前实现未使用,保留以匹配 SPI 签名)
- * @return 已注入 MongoTemplate 的 Delegate 实例;构造异常时返回 null
- */
- @Override
- @SuppressWarnings({"unchecked", "rawtypes"})
- public RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass) {
- try {
- return new MongoRepositoryDelegate(mongoTemplate, poClass);
- } catch (Exception e) {
- return null;
- }
- }
-}
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoRepositoryDelegate.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoRepositoryDelegate.java
index f1afbbf..a447592 100644
--- a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoRepositoryDelegate.java
+++ b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/repository/MongoRepositoryDelegate.java
@@ -2,18 +2,23 @@
import cn.structure.common.vo.ReqPage;
import cn.structure.common.vo.ResPage;
+import cn.structure.infra.repository.GenericTypeResolver;
import cn.structure.infra.repository.RepositoryDelegate;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
+import jakarta.persistence.Id;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* 基于 MongoDB 的 RepositoryDelegate 适配实现
@@ -30,229 +35,171 @@
* 实现说明:
* 持久化对象类型(MongoDB Document)
* @param
- * 创建后由 {@link MongoDelegateBeanPostProcessor} 通过 setter 注入依赖。
- */
+ protected Class poClass;
+ protected Class ) GenericTypeResolver.resolvePoClass(getClass());
+ this.idClass = (Class
- * 委托给 {@link MongoTemplate#save(Object)},由 MongoDB 依据 _id 自动判断新增或更新。
- *
- * @param entity 实体对象,为 null 时返回 null
- * @return 保存后的实体(与入参同一引用)
- */
@Override
- public T save(T entity) {
+ public E save(E entity) {
if (entity == null) {
return null;
}
- T saved = mongoTemplate.save(entity);
- log.debug("Saved entity: {}", saved);
- return saved;
+ P po = toPo(entity);
+ P savedPo = mongoTemplate.save(po);
+ log.debug("Saved entity: {}", savedPo);
+ return toEntity(savedPo);
}
- /**
- * 根据主键删除文档。
- *
- * @param id 主键值,为 null 时不执行任何操作
- */
@Override
public void removeById(ID id) {
- if (id != null) {
- // 按主键字段构建等值条件并删除
+ if (id != null && poClass != null) {
Query query = new Query(Criteria.where(idFieldName).is(id));
- mongoTemplate.remove(query, entityClass);
+ mongoTemplate.remove(query, poClass);
log.debug("Removed entity: id={}", id);
}
}
- /**
- * 根据主键查询文档。
- *
- * @param id 主键值,为 null 时返回 null
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T findById(ID id) {
- if (id == null) {
+ public E findById(ID id) {
+ if (id == null || poClass == null) {
return null;
}
Query query = new Query(Criteria.where(idFieldName).is(id));
- T entity = mongoTemplate.findOne(query, entityClass);
- log.debug("Find by id: id={}, found={}", id, entity != null);
- return entity;
+ P po = mongoTemplate.findOne(query, poClass);
+ log.debug("Find by id: id={}, found={}", id, po != null);
+ return toEntity(po);
}
- /**
- * 根据主键查询(与 findById 等价,语义上用于"读模型")。
- *
- * @param id 主键值
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T queryById(ID id) {
+ public E queryById(ID id) {
return findById(id);
}
- /**
- * 根据主键查询并以 {@link Optional} 包装返回。
- *
- * @param id 主键值
- * @return 包含实体的 Optional,未找到时为 {@link Optional#empty()}
- */
@Override
- public Optional
- * 通过反射构建 {@link Query},取首条匹配;多于一条时仅返回首条。
- *
- * @param condition 查询条件对象,为 null 时返回 null
- * @return 首条匹配记录,无匹配时返回 null
- */
@Override
- public T queryOne(T condition) {
+ public E queryOne(E condition) {
if (condition == null) {
return null;
}
Query query = buildQuery(condition);
- return mongoTemplate.findOne(query, entityClass);
+ P po = mongoTemplate.findOne(query, poClass);
+ return toEntity(po);
}
- /**
- * 根据条件查询单条记录,并以 {@link Optional} 包装返回。
- *
- * @param condition 查询条件对象
- * @return 包含首条匹配记录的 Optional
- */
@Override
- public Optional
- * 条件为 null 时查询全部;否则按非空字段构建等值 {@link Query}。
- *
- * @param condition 查询条件对象,可为 null
- * @return 匹配的实体列表,无匹配时返回空列表
- */
@Override
- public List
- * 通过 {@link MongoTemplate#count(Query, Class)} 获取总数,
- * 再用 {@link PageRequest} 切片查询当前页记录。
- *
- * @param reqPage 分页请求(页码从 1 开始、每页大小,为 null 时取默认 1/10)
- * @return 分页结果,含当前页、总页数、总条数、当前页记录
- */
@Override
- public ResPage
- * 当检测到 MyBatis Plus 相关依赖({@link com.baomidou.mybatisplus.core.mapper.BaseMapper})时自动配置,
- * 注册 MyBatis Plus 持久化所需的核心组件,使其与仓储框架无缝集成。
- *
- * 注册的 Bean:
- *
- * 工作机制:
- *
- * 配置方式:
- *
- * 负责根据 PO 类自动查找对应的 BaseMapper,并创建 MybatisPlusRepositoryDelegate 实例。
- * 当 RepositoryFacade 需要获取特定类型的 RepositoryDelegate 时,会通过此工厂进行创建。
- *
- * @param applicationContext Spring 应用上下文,用于查找 Mapper Bean
- * @return MybatisPlusDelegateFactory 实例
- */
- @Bean
- public MybatisPlusDelegateFactory mybatisPlusDelegateFactory(ApplicationContext applicationContext) {
- return new MybatisPlusDelegateFactory(applicationContext);
- }
-
- /**
- * 创建 MyBatis Plus 委托 Bean 后处理器
- *
- * 在 Bean 初始化完成后,自动为带有 {@link cn.structure.infra.annotations.DelegateFor} 注解的
- * MybatisPlusRepositoryDelegate 实现类注入对应的 BaseMapper。
- *
- * 处理逻辑:
- * 1. 扫描所有 Bean,筛选出 MybatisPlusRepositoryDelegate 的实例
- * 2. 检查是否存在 {@link cn.structure.infra.annotations.DelegateFor} 注解
- * 3. 根据注解中指定的 PO 类查找对应的 BaseMapper
- * 4. 将找到的 BaseMapper 注入到 Delegate 实例中
- *
- * @return MybatisPlusDelegateBeanPostProcessor 实例
- */
- @Bean
- public MybatisPlusDelegateBeanPostProcessor mybatisPlusDelegateBeanPostProcessor() {
- return new MybatisPlusDelegateBeanPostProcessor();
- }
}
diff --git a/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusDelegateBeanPostProcessor.java b/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusDelegateBeanPostProcessor.java
deleted file mode 100644
index 4486a52..0000000
--- a/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusDelegateBeanPostProcessor.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package cn.structure.infra.mybatis.plus.repository;
-
-import cn.structure.infra.annotations.DelegateFor;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-
-/**
- * MyBatis Plus RepositoryDelegate 的 BeanPostProcessor,负责为用户自定义 Delegate 子类自动注入 BaseMapper。
- *
- * 在仓储框架中,业务方可继承 {@link MybatisPlusRepositoryDelegate} 实现自定义 Delegate,并通过
- * {@link DelegateFor} 注解声明其服务的 PO 类型。本后处理器在 Bean 初始化完成后:
- *
- * 与 {@link MybatisPlusDelegateFactory} 的分工:工厂负责"无自定义 Delegate 时自动创建",
- * 本处理器负责"已有自定义 Delegate 时补齐依赖",二者协同保证 RepositoryFacade 总能拿到可用的 Delegate。
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
-@Slf4j
-public class MybatisPlusDelegateBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
-
- /** Spring 上下文,用于按类型或名称查找 BaseMapper Bean */
- private ApplicationContext applicationContext;
-
- /**
- * 注入 Spring 应用上下文,供后续按类型/名称查询 Bean。
- *
- * @param applicationContext Spring 应用上下文
- * @throws BeansException 上下文注入异常
- */
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
-
- /**
- * 在 Bean 初始化完成后,对自定义 MybatisPlusRepositoryDelegate 实现类注入 BaseMapper。
- *
- * 仅当 Bean 同时满足:是 {@link MybatisPlusRepositoryDelegate} 实例、且类上标注了
- * {@link DelegateFor} 注解、注解显式指定了 PO 类型时,才执行注入。
- *
- * @param bean 待处理的 Bean 实例
- * @param beanName Bean 名称
- * @return 原始 Bean(已注入依赖),未匹配类型时原样返回
- * @throws BeansException 处理过程中的异常
- */
- @SuppressWarnings({"unchecked", "rawtypes"})
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof MybatisPlusRepositoryDelegate) {
- MybatisPlusRepositoryDelegate delegate = (MybatisPlusRepositoryDelegate) bean;
- // 读取 @DelegateFor 注解,识别该 Delegate 服务的 PO 类型
- DelegateFor annotation = bean.getClass().getAnnotation(DelegateFor.class);
- if (annotation != null && annotation.po() != void.class) {
- try {
- // 按 PO 类型约定查找对应的 BaseMapper
- Object mapper = findMapperByPoClass(annotation.po());
- if (mapper != null) {
- // 反向注入 BaseMapper 与 PO 类型,使自定义 Delegate 可正常工作
- delegate.setBaseMapper((BaseMapper) mapper);
- delegate.setEntityClass(annotation.po());
- log.info("Injected BaseMapper into MybatisPlusRepositoryDelegate: {}", beanName);
- } else {
- log.warn("No BaseMapper found for PO class {} in MybatisPlusRepositoryDelegate {}", annotation.po().getSimpleName(), beanName);
- }
- } catch (Exception e) {
- log.warn("Failed to inject BaseMapper into MybatisPlusRepositoryDelegate {}: {}", beanName, e.getMessage());
- }
- }
- }
- return bean;
- }
-
- /**
- * 根据 PO 类查找对应的 BaseMapper Bean。
- *
- * 查找策略(按优先级):
- *
- * 实现 {@link RepositoryDelegateFactory} SPI,自动创建 {@link MybatisPlusRepositoryDelegate} 实例。
- * 在仓储框架中,当 {@code RepositoryFacade} 找不到用户自定义的 Delegate 时,会通过本工厂按 PO 类型
- * 查找对应的 {@link BaseMapper},并创建默认 Delegate 实例。
- *
- * 与 {@link MybatisPlusDelegateBeanPostProcessor} 的分工:本工厂负责"无自定义 Delegate 时创建默认实现",
- * BeanPostProcessor 负责"已有自定义子类时补齐依赖"。
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
-public class MybatisPlusDelegateFactory implements RepositoryDelegateFactory {
-
- /** Spring 上下文,用于按类型/名称查找 BaseMapper Bean */
- private final ApplicationContext applicationContext;
-
- /**
- * 构造工厂,注入 Spring 应用上下文。
- *
- * @param applicationContext Spring 应用上下文,用于查找 Mapper Bean
- */
- public MybatisPlusDelegateFactory(ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
- }
-
- /**
- * 返回该工厂支持的仓储类型,用于 SPI 路由匹配。
- *
- * @return 固定返回 {@link RepositoryType#MYBATIS_PLUS}
- */
- @Override
- public RepositoryType getType() {
- return RepositoryType.MYBATIS_PLUS;
- }
-
- /**
- * 为指定 PO 类型创建 {@link MybatisPlusRepositoryDelegate} 实例。
- *
- * 内部按约定({@code .po.} → {@code .mapper.}、PO 后缀 → Mapper)查找对应 BaseMapper,
- * 找不到时返回 null(由上层 RepositoryFacade 继续尝试其他工厂或抛出异常)。
- *
- * @param poClass PO 实体类型
- * @param idClass 主键类型(当前实现未使用,保留以匹配 SPI 签名)
- * @return 已注入 BaseMapper 的 Delegate 实例;未找到 Mapper 时返回 null
- */
- @Override
- @SuppressWarnings({"unchecked", "rawtypes"})
- public RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass) {
- try {
- BaseMapper mapper = (BaseMapper) findMapperByPoClass(poClass);
- if (mapper == null) {
- return null;
- }
- return new MybatisPlusRepositoryDelegate(mapper, poClass);
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * 根据 PO 类查找对应的 BaseMapper Bean。
- *
- * 查找策略(按优先级):
- *
+ * 注意:Entity ↔ PO 转换在此层完成,Facade 层只操作领域实体。
*
- * @param 持久化对象类型(PO)
* @param
- * 创建后由 {@link MybatisPlusDelegateBeanPostProcessor} 通过 setter 注入依赖。
- */
+public class MybatisPlusRepositoryDelegate baseMapper;
+ protected Class poClass;
+ protected Class ) GenericTypeResolver.resolvePoClass(getClass());
+ this.idClass = (Class
- * 根据 ID 字段值是否为空自动选择策略:ID 为空执行 insert,否则执行 updateById。
- *
- * @param entity 实体对象,为 null 时直接返回 null
- * @return 保存后的实体(与入参同一引用)
- */
@Override
- public T save(T entity) {
+ public E save(E entity) {
if (entity == null) {
return null;
}
- // 反射读取主键值,决定走新增还是更新分支
- ID id = getIdValue(entity);
+ P po = toPo(entity);
+ ID id = getIdValue(po);
if (id == null) {
- baseMapper.insert(entity);
+ baseMapper.insert(po);
} else {
- baseMapper.updateById(entity);
+ baseMapper.updateById(po);
}
log.debug("Saved entity: id={}, entity={}", id, entity);
- return entity;
+ return toEntity(po);
}
- /**
- * 根据主键删除记录。
- *
- * @param id 主键值,为 null 时不执行任何操作
- */
@Override
public void removeById(ID id) {
if (id != null) {
@@ -148,137 +142,128 @@ public void removeById(ID id) {
}
}
- /**
- * 根据主键查询实体。
- *
- * @param id 主键值,为 null 时返回 null
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T findById(ID id) {
+ public E findById(ID id) {
if (id == null) {
return null;
}
- T entity = baseMapper.selectById((Serializable) id);
- log.debug("Find by id: id={}, found={}", id, entity != null);
- return entity;
+ P po = baseMapper.selectById((Serializable) id);
+ log.debug("Find by id: id={}, found={}", id, po != null);
+ return toEntity(po);
}
- /**
- * 根据主键查询(与 findById 等价,语义上用于"读模型")。
- *
- * @param id 主键值
- * @return 实体对象,未找到时返回 null
- */
@Override
- public T queryById(ID id) {
+ public E queryById(ID id) {
return findById(id);
}
- /**
- * 根据主键查询并以 {@link Optional} 包装返回,避免空指针。
- *
- * @param id 主键值
- * @return 包含实体的 Optional,未找到时为 {@link Optional#empty()}
- */
@Override
- public Optional
- * 将条件对象非空字段组装为 {@link QueryWrapper},取结果集第一条;多于一条时仅返回首条。
- *
- * @param condition 查询条件对象,为 null 时返回 null
- * @return 首条匹配记录,无匹配时返回 null
- */
@Override
- public T queryOne(T condition) {
+ public E queryOne(E condition) {
if (condition == null) {
return null;
}
- QueryWrapper queryWrapper = buildQueryWrapper(poCondition);
+ List results = baseMapper.selectList(queryWrapper);
+ P po = results.isEmpty() ? null : results.get(0);
+ return toEntity(po);
}
- /**
- * 根据条件查询单条记录,并以 {@link Optional} 包装返回。
- *
- * @param condition 查询条件对象
- * @return 包含首条匹配记录的 Optional
- */
@Override
- public Optional
- * 条件为 null 时等价于全表查询;否则按非空字段等值匹配。
- *
- * @param condition 查询条件对象,可为 null
- * @return 匹配的实体列表,无匹配时返回空列表
- */
@Override
- public List poList = baseMapper.selectList(null);
+ return toEntityList(poList);
}
- QueryWrapper queryWrapper = buildQueryWrapper(poCondition);
+ List poList = baseMapper.selectList(queryWrapper);
+ return toEntityList(poList);
}
- /**
- * 分页查询。
- *
- * 委托 MyBatis Plus 的 {@link Page} 执行分页,实际分页 SQL 由分页拦截器按方言生成。
- *
- * @param reqPage 分页请求(页码、每页大小,为 null 时取默认 1/10)
- * @return 分页结果,包含当前页、总页数、总条数、当前页记录
- */
@Override
- public ResPage page = new Page<>(pageNum, pageSize);
+ IPage result = baseMapper.selectPage(page, null);
- // 将 MyBatis Plus 分页结果转写为统一 ResPage
- ResPage
- * 反射读取所有字段(含父类),将驼峰字段名转为下划线列名后拼接 eq 条件。
- *
- * @param condition 条件对象
- * @return 已填充等值条件的 QueryWrapper
- */
- private QueryWrapper poList = toPoList(entities);
+ poList.forEach(baseMapper::insert);
+ return toEntityList(poList);
+ }
+
+ @Override
+ public void removeBatchByIds(List poList = baseMapper.selectBatchIds(ids.stream()
+ .map(id -> (Serializable) id)
+ .toList());
+ return toEntityList(poList);
+ }
+
+ @Override
+ public long count(E condition) {
+ if (condition == null) {
+ return baseMapper.selectCount(null);
+ }
+ P poCondition = toPo(condition);
+ QueryWrapper queryWrapper = buildQueryWrapper(poCondition);
+ return baseMapper.selectCount(queryWrapper);
+ }
+
+ @Override
+ public boolean exists(E condition) {
+ return count(condition) > 0;
+ }
+
+ private QueryWrapper buildQueryWrapper(P condition) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
try {
Field[] fields = getAllFields(condition.getClass());
for (Field field : fields) {
field.setAccessible(true);
Object value = field.get(condition);
if (value != null) {
- // 字段名驼峰转下划线,以匹配数据库列名
queryWrapper.eq(camelToUnderline(field.getName()), value);
}
}
@@ -288,12 +273,6 @@ private QueryWrapper poList) {
+ if (poList == null || poList.isEmpty()) {
return List.of();
}
- return baseMapper.selectBatchIds(ids.stream()
- .map(id -> (Serializable) id)
- .toList());
+ return poList.stream()
+ .map(this::toEntity)
+ .collect(Collectors.toList());
}
- /**
- * 按条件统计记录数。
- *
- * @param condition 条件对象,为 null 时统计全表
- * @return 匹配的记录数
- */
- @Override
- public long count(T condition) {
- if (condition == null) {
- return baseMapper.selectCount(null);
+ protected P toPo(E entity) {
+ if (entity == null) {
+ return null;
+ }
+ try {
+ P po = poClass.getDeclaredConstructor().newInstance();
+ BeanUtils.copyProperties(entity, po);
+ return po;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to convert entity to PO", e);
}
- QueryWrapper toPoList(List
+ * 仅包含基础字段定义,不包含任何框架特定的注解。
+ * 各持久化技术模块应创建各自的专用 PO,继承或参考此类。
+ *
+ * @author chuck
+ * @version 1.0.2
+ * @since 2026/6/28
+ */
@Data
-@TableName("t_user")
-@Entity
-@Table(name = "t_user")
-@Document(collection = "t_user")
-@org.springframework.data.elasticsearch.annotations.Document(indexName = "t_user")
public class UserPO {
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @TableId(type = IdType.AUTO)
private Long id;
private String username;
diff --git a/structure-infra-sample/structure-infra-sample-core/src/main/java/cn/structure/infra/sample/infra/repository/AbstractUserRepositoryImpl.java b/structure-infra-sample/structure-infra-sample-core/src/main/java/cn/structure/infra/sample/infra/repository/AbstractUserRepositoryImpl.java
index 20fef2c..293f574 100644
--- a/structure-infra-sample/structure-infra-sample-core/src/main/java/cn/structure/infra/sample/infra/repository/AbstractUserRepositoryImpl.java
+++ b/structure-infra-sample/structure-infra-sample-core/src/main/java/cn/structure/infra/sample/infra/repository/AbstractUserRepositoryImpl.java
@@ -3,23 +3,23 @@
import cn.structure.infra.repository.RepositoryFacade;
import cn.structure.infra.sample.domain.entity.UserEntity;
import cn.structure.infra.sample.domain.repository.UserRepository;
-import cn.structure.infra.sample.infra.po.UserPO;
import cn.structure.infra.sample.infra.repository.delegate.UserRepositoryDelegate;
/**
* 用户仓储基类
*
- * 提供通用的仓储实现,各存储技术模块可以继承此类并指定具体的存储类型
+ * 提供通用的仓储实现,各存储技术模块可以继承此类并指定具体的存储类型。
+ *
+ * 注意:PO 类型已从 Facade 层移除,Entity ↔ PO 转换由具体的 Delegate 实现负责。
*
* @author chuck
- * @version 1.0.1
+ * @version 1.0.2
* @since 2026/6/28
*/
-public abstract class AbstractUserRepositoryImpl extends RepositoryFacade
* 用户仓储代理
- *
+ * 定义用户仓储的委托接口,面向领域实体(UserEntity)。
+ * 具体实现负责内部的 Entity ↔ PO 转换。
*
* @author chuck
- * @version 1.0.1
+ * @version 1.0.2
* @since 2026/6/28
*/
-public interface UserRepositoryDelegate extends RepositoryDelegate
- * 模拟 Elasticsearch 等读数据源,只实现读操作,不实现写操作。
- *
- * 特点:
- * - 只实现 IQueryDelegate 接口(只读能力)
- * - 不实现 RepositoryDelegate 接口(无写操作)
- * - 可以与写代理是完全不同的类型
- * - 如果读代理执行失败,自动回退到写代理(baseDelegate)
- *
- * delegateType = READ 表示这是读代理
- */
@Slf4j
@Component
-@DelegateFor(
- name = "userCqrsRepository",
- po = UserPO.class,
- delegateType = DelegateType.READ,
- type = RepositoryType.ELASTICSEARCH
-
-)
-public class UserReadDelegate extends ElasticsearchRepositoryDelegate
* 同时也可以处理读操作(作为读操作的兜底)
- *
- * delegateType = BASE 表示这是基础/写代理
*/
@Slf4j
@Component
-@DelegateFor(
- name = "userCqrsRepository",
- po = UserPO.class,
- delegateType = DelegateType.BASE,
- type = RepositoryType.MYBATIS_PLUS
-)
-public class UserWriteDelegate extends MybatisPlusRepositoryDelegate
- *
- *
- *
- *
- *
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
@AutoConfiguration
@ConditionalOnClass(name = "org.springframework.data.jpa.repository.JpaRepository")
@EnableJpaRepositories
@EnableTransactionManagement
public class JpaAutoConfiguration {
-
- /**
- * 创建 JPA 仓储委托工厂
- *
- *
- *
- *
- *
- * @return EntityManager 实例,无法解析时返回 null
- */
- private EntityManager getEntityManager() {
- // 1) by name:优先按 "entityManager" 名称获取已注册的容器 Bean
- try {
- Object bean = applicationContext.getBean("entityManager");
- if (bean instanceof EntityManager) {
- return (EntityManager) bean;
- }
- } catch (Exception e) {
- log.debug("entityManager bean not found by name");
- }
-
- // 2) by type → create:通过 EntityManagerFactory 创建新的 EntityManager
- try {
- EntityManagerFactory factory = applicationContext.getBean(EntityManagerFactory.class);
- if (factory != null) {
- return factory.createEntityManager();
- }
- } catch (Exception e) {
- log.debug("EntityManagerFactory bean not found");
- }
-
- return null;
- }
-}
\ No newline at end of file
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateFactory.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateFactory.java
deleted file mode 100644
index 14ed7bf..0000000
--- a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateFactory.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package cn.structure.infra.jpa.repository;
-
-import cn.structure.infra.repository.RepositoryDelegate;
-import cn.structure.infra.repository.RepositoryDelegateFactory;
-import cn.structure.infra.repository.RepositoryType;
-
-import jakarta.persistence.EntityManager;
-
-/**
- * JPA 仓储委托工厂
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
@AutoConfiguration
@ConditionalOnClass(name = "org.springframework.data.mongodb.core.MongoTemplate")
@ConditionalOnProperty(prefix = "structure.infra", name = "type", havingValue = "MONGODB", matchIfMissing = true)
@EnableMongoRepositories
public class MongoAutoConfiguration {
-
- /**
- * 创建 MongoDB 仓储委托工厂
- *
- *
- *
*
*
- * @param
- *
- *
- *
- *
- *
- *
- * @author chuck
- * @version 1.0.1
- * @since 2026/6/28
- */
@AutoConfiguration
@ConditionalOnClass(name = "com.baomidou.mybatisplus.core.mapper.BaseMapper")
@ConditionalOnProperty(prefix = "structure.infra", name = "type", havingValue = "MYBATIS_PLUS", matchIfMissing = true)
public class MybatisPlusAutoConfiguration {
-
- /**
- * 创建 MyBatis Plus 仓储委托工厂
- *
- *
- *
- *
- *
- * @param poClass PO 类型
- * @return 对应的 BaseMapper Bean,未找到返回 null
- */
- private Object findMapperByPoClass(Class> poClass) {
- // 约定:po 包下的 XxxPO 对应 mapper 包下的 XxxMapper
- String poClassName = poClass.getName();
- String mapperClassName = poClassName.replace(".po.", ".mapper.")
- .replace("PO", "Mapper");
- try {
- Class> mapperClass = Class.forName(mapperClassName);
- return applicationContext.getBean(mapperClass);
- } catch (ClassNotFoundException e) {
- log.debug("Mapper class not found: {}", mapperClassName);
- } catch (Exception e) {
- log.debug("Failed to get mapper bean: {}", e.getMessage());
- }
-
- // 兜底:按 Bean 简单名查找(如 "XxxMapper")
- String simpleMapperName = poClass.getSimpleName().replace("PO", "Mapper");
- try {
- return applicationContext.getBean(simpleMapperName);
- } catch (Exception e) {
- log.debug("Failed to get mapper by name: {}", simpleMapperName);
- }
-
- return null;
- }
-}
\ No newline at end of file
diff --git a/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusDelegateFactory.java b/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusDelegateFactory.java
deleted file mode 100644
index 64b7c26..0000000
--- a/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusDelegateFactory.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package cn.structure.infra.mybatis.plus.repository;
-
-import cn.structure.infra.repository.RepositoryDelegate;
-import cn.structure.infra.repository.RepositoryDelegateFactory;
-import cn.structure.infra.repository.RepositoryType;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import org.springframework.context.ApplicationContext;
-
-/**
- * MyBatis Plus 仓储委托工厂
- *
- *
- *
- * @param poClass PO 类型
- * @return 对应的 BaseMapper Bean,未找到返回 null
- */
- private Object findMapperByPoClass(Class> poClass) {
- // 约定:po 包下的 XxxPO 对应 mapper 包下的 XxxMapper
- String poClassName = poClass.getName();
- String mapperClassName = poClassName.replace(".po.", ".mapper.")
- .replace("PO", "Mapper");
- try {
- Class> mapperClass = Class.forName(mapperClassName);
- return applicationContext.getBean(mapperClass);
- } catch (Exception e) {
- // 兜底:遍历 Bean 名称,匹配以 XxxMapper 结尾的 Bean
- String simpleMapperName = poClass.getSimpleName().replace("PO", "Mapper");
- for (String beanName : applicationContext.getBeanDefinitionNames()) {
- if (beanName.endsWith(simpleMapperName)) {
- return applicationContext.getBean(beanName);
- }
- }
- return null;
- }
- }
-}
diff --git a/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusRepositoryDelegate.java b/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusRepositoryDelegate.java
index 55202f2..fa79939 100644
--- a/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusRepositoryDelegate.java
+++ b/structure-infra-mybatis-plus-starter/src/main/java/cn/structure/infra/mybatis/plus/repository/MybatisPlusRepositoryDelegate.java
@@ -2,18 +2,23 @@
import cn.structure.common.vo.ReqPage;
import cn.structure.common.vo.ResPage;
+import cn.structure.infra.repository.GenericTypeResolver;
import cn.structure.infra.repository.RepositoryDelegate;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import jakarta.persistence.Id;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* 基于 MyBatis Plus 的 RepositoryDelegate 适配实现
@@ -31,115 +36,104 @@
*
*
+ *