refactor(service): 统一Service缓存为VO对象并优化关联实体处理

重构Service类实现,将QueryService泛型参数调整为VO类型,确保缓存VO对象而非实体。优化关联实体处理逻辑,减少重复代码。修改findById方法返回VO对象,新增getById方法获取实体。更新相关调用点以适配新接口。

调整WebSocket处理、控制器及Service实现,确保数据类型一致性。完善文档记录重构过程及发现的问题。为后续优化提供基础架构支持。
This commit is contained in:
2025-09-29 19:31:51 +08:00
parent 64471b46f8
commit 49413ad473
167 changed files with 6840 additions and 1811 deletions

View File

@@ -13,6 +13,7 @@
- **Service实现模式**: 多个Service类同时实现IEntityService和VoableService接口分别指定实体类和VO类的泛型参数
- **缓存配置**: 使用@CacheConfig注解配置缓存名称,方法级缓存使用@Cacheable@CacheEvict等注解
- **缓存问题**: 当前使用实体类进行缓存由于Hibernate代理对象序列化问题可能导致懒加载异常
- **实体类和Vo类**: 都定义在 common 模块中,确保在不同模块之间的引用和使用
## 2. 需求理解确认
@@ -27,13 +28,16 @@
- **修改内容**:
- Service类继承IEntityService接口泛型类型保持为Model实体类
- Service类继承QueryService接口泛型类型修改为Vo视图对象
- Service类继承VoableService接口泛型类型保持为Model实体类和Vo视图对象
- 实体类继承Voable接口提供toVo方法实现从实体类到VO的转换
- **影响范围**: 需要同步修改Service类中实现的接口方法的参数和返回类型
- **任务边界**: 不修改接口定义本身,只修改实现类的泛型参数和相关方法实现
### 2.3 需求理解
- 当前Service类同时实现IEntityService<Entity>和VoableService<Entity, Vo>接口部分还实现了QueryService接口
- 需要调整Service类的接口实现使IEntityService保持使用Model类型QueryService使用Vo类型
- 修改后,Service类需要根据不同接口的要求实现对应的方法
- 当前Service类同时实现`IEntityService<Entity>``QueryService<Vo>`接口,还需实现`VoableService<Entity, Vo>`接口
- 需要调整Service类的接口实现使IEntityService保持使用实体类类型QueryService使用Vo类型
- Service类需要同时实现IEntityService<实体类>和QueryService<Vo类>接口,并根据不同接口的要求实现对应的方法,IEntityService和QueryService接口有同名的方法按各自结构定义实现
- 需要确保缓存注解的键值表达式仍然有效
- 需要保证修改后系统功能正常运行
- 使用VO替代实体类进行缓存避免Hibernate代理对象序列化问题彻底规避懒加载异常
@@ -41,7 +45,9 @@
## 3. 疑问澄清
1. **数据转换问题**: 如何处理从实体类到VO的转换和从VO到实体类的转换
- 系统中已有VoableService接口提供updateByVo方法可能需要利用现有转换机制
- 实体类应该实现Voable接口提供toVo方法实现从实体类到VO的转换
- Service类实现VoableService接口提供updateByVo方法实现从Vo到实体类的转换,参考 ProjectQuotationService 的 updateByVo方法
2. **缓存键表达式**: 修改泛型后,缓存注解中的键表达式(如@CacheEvict(key = "#p0.id"))是否需要修改?
- 需要确认VO类是否与实体类具有相同的属性结构
@@ -53,35 +59,88 @@
- 需要确保事务边界正确维护
5. **查询规范**: getSpecification方法如何适配从Entity到Vo的转换
- Specification是基于JPA实体类的这可能需要特殊处理
- 解决方案Service类同时实现IEntityService<实体类>和QueryService<Vo类>接口
- 在IEntityService<实体类>接口中保持getSpecification方法返回基于JPA实体的Specification
```java
public Page<CompanyExtendInfo> findAll(Specification<CompanyExtendInfo> spec, Pageable pageable) {
return repository.findAll(spec, pageable);
}
```
- 在QueryService<Vo类>接口的findAll方法中先使用基于实体类的Specification查询数据然后通过map操作将实体转换为Vo
```java
@Override
public Page<ProjectCostVo> findAll(JsonNode paramsNode, Pageable pageable) {
Specification<ProjectCost> spec = null;
if (paramsNode.has(ServiceConstant.KEY_SEARCH_TEXT)) {
spec = getSpecification(paramsNode.get(ServiceConstant.KEY_SEARCH_TEXT).asText());
}
// field
spec = SpecificationUtils.andParam(spec, paramsNode, "project");
if (paramsNode.has("project.customer")) {
Integer customerId = paramsNode.get("project.customer").asInt();
spec = SpecificationUtils.and(spec, (root, query, builder) -> {
return builder.equal(root.get("project").get("customer").get("id"), customerId);
});
}
Page<ProjectCost> page = findAll(spec, pageable);
return page.map(ProjectCost::toVo);
}
```
- 实体类实现Voable<Vo类>接口提供toVo方法进行转换
```java
@Cacheable(key = "#p0")
@Override
public ProjectCostVo findById(Integer id) {
ProjectCost cost = getById(id);
if (cost != null) {
return cost.toVo();
}
return null;
}
```
6. **缓存对象转换**: 如何确保缓存中存储的是VO对象而不是实体类对象
- 需要确保所有缓存的方法都返回VO对象并在存储前完成转换
解决方案通过以下机制确保缓存中存储VO对象
- 服务类同时实现IEntityService<实体类>和QueryService<Vo类>接口
- QueryService接口的方法如findById添加@Cacheable注解
- 在这些缓存方法的实现中查询实体后通过调用实体类的toVo()方法转换为Vo对象再返回
- 实体类实现Voable接口提供toVo()方法进行转换
- 示例实现:
```java
@Cacheable(key = "#id")
@Override
public CompanyFileTypeLocalVo findById(Integer id) {
Optional<CompanyFileTypeLocal> optional = repository.findById(id);
return optional.map(CompanyFileTypeLocal::toVo).orElse(null);
}
```
这种方式确保了缓存中存储的是转换后的Vo对象而不是原始实体对象。
7. **WebSocket服务影响**: WebSocketServerCallbackManager类直接调用IEntityService接口修改泛型后会对WebSocket服务产生什么影响?
- 需要分析WebSocketServerCallbackManager中的类型处理逻辑,确保其能够适应新的泛型参数
- 特别关注createNewEntity、findEntityTypeInInterfaces等方法这些方法依赖于IEntityService的泛型参数
7. **WebSocket服务影响**: WebSocketServerCallbackManager类直接调用IEntityService接口由于我们保持IEntityService泛型为实体类因此对WebSocket服务的影响较小
- WebSocketServerCallbackManager中的createNewEntity、findEntityTypeInInterfaces等方法主要依赖于IEntityService的实体类泛型
- 需要确认这些方法是否兼容当前的实现方式
8. **任务管理影响**: WebSocketServerTaskManager类中处理的任务是否会受到接口泛型修改的影响?
- 需要分析任务执行过程中是否依赖于IEntityService接口的方法调用
8. **任务管理影响**: WebSocketServerTaskManager类中处理的任务主要依赖于IEntityService接口由于我们保持了IEntityService的泛型为实体类对任务管理的影响应该较小
## 4. 初步决策
基于现有项目代码分析,做出以下初步决策:
1. **泛型修改策略**: 对于每个注解了@CacheConfig的Service类IEntityService<T>的泛型T从实体类改为对应的VO
1. **泛型修改策略**: 对于每个注解了@CacheConfig的Service类同时实现IEntityService<实体类>和QueryService<Vo类>接口保持IEntityService泛型为实体类QueryService泛型为Vo
2. **方法适配方案**:
- 对于返回类型为T的方法需要在方法内部进行实体类到VO的转换
- 对于参数为T的方法需要在方法内部进行VO到实体类的转换
- 对于findAll等查询方法需要修改Specification的泛型类型
- IEntityService接口的方法保持基于实体类的实现
- QueryService接口的方法如findById、findAll在内部先查询实体再通过实体类的toVo()方法转换为Vo对象返回
- 实体类实现Voable接口提供toVo()方法进行转换
3. **缓存键处理**: 假设VO类与实体类具有相同的ID属性和其他缓存键中使用的属性,因此缓存注解可能不需要修改
3. **缓存键处理**: QueryService接口的缓存方法如findById使用与实体类相同的ID属性作为缓存键由于Vo类通常与实体类具有相同的ID属性,因此缓存注解可能不需要修改
4. **依赖处理**: 需要评估并处理所有调用修改后Service的组件确保它们适应新的接口定义
5. **特殊方法处理**: 对于getSpecification等基于JPA实体的方法可能需要特殊处理或保留原有实现
5. **特殊方法处理**: 对于getSpecification等基于JPA实体的方法保持在IEntityService接口中返回基于实体类的Specification
6. **缓存策略**: 确保所有标注@Cacheable的方法都返回VO对象并在存储前完成从实体类到VO的转换以避免代理对象序列化问题
6. **缓存策略**: 确保所有标注@Cacheable的方法主要是QueryService接口中的方法都返回VO对象并在存储前完成从实体类到VO的转换以避免代理对象序列化问题
这些决策将在后续的共识和设计阶段进一步细化和确认。