# Server模块Service缓存调整为Vo对象对齐文档 ## 1. 项目上下文分析 ### 1.1 项目结构与技术栈 - **项目**: Contract-Manager - **模块**: server模块 - **技术栈**: Java 21, Spring Boot 3.3.7, Spring Data JPA 3.3.7, Redis (用于缓存) ### 1.2 现有代码模式 - **接口结构**: IEntityService接口定义了基础的CRUD操作,泛型T当前用于指定实体类类型 - **VoableService接口**: 定义了updateByVo(M model, Vo vo)方法,用于将VO对象的值更新到实体对象 - **Service实现模式**: 多个Service类同时实现IEntityService和VoableService接口,分别指定实体类和VO类的泛型参数 - **缓存配置**: 使用@CacheConfig注解配置缓存名称,方法级缓存使用@Cacheable、@CacheEvict等注解 - **缓存问题**: 当前使用实体类进行缓存,由于Hibernate代理对象序列化问题,可能导致懒加载异常 - **实体类和Vo类**: 都定义在 common 模块中,确保在不同模块之间的引用和使用 ## 2. 需求理解确认 ### 2.1 原始需求 > server模块中注解了 @CacheConfig的Service,调整接口泛型参数,涉及到Service上各个方法的的修改,方法修改相关引用方法的地方也要修改 ### 2.1.1 扩展需求 > 使用VO替代实体缓存,因为使用redis服务,需要避免代理对象序列化,彻底规避懒加载问题 ### 2.2 边界确认 - **目标范围**: server模块中所有注解了@CacheConfig的Service类 - **修改内容**: - Service类继承IEntityService接口,泛型类型保持为Model(实体类) - Service类继承QueryService接口,泛型类型修改为Vo(视图对象) - Service类继承VoableService接口,泛型类型保持为Model(实体类)和Vo(视图对象) - 实体类继承Voable接口,提供toVo方法实现从实体类到VO的转换 - **影响范围**: 需要同步修改Service类中实现的接口方法的参数和返回类型 - **任务边界**: 不修改接口定义本身,只修改实现类的泛型参数和相关方法实现 ### 2.3 需求理解 - 当前Service类同时实现`IEntityService`和`QueryService`接口,还需实现`VoableService`接口 - 需要调整Service类的接口实现,使IEntityService保持使用实体类类型,QueryService使用Vo类型 - Service类需要同时实现IEntityService<实体类>和QueryService接口,并根据不同接口的要求实现对应的方法,IEntityService和QueryService接口有同名的方法,按各自结构定义实现 - 需要确保缓存注解的键值表达式仍然有效 - 需要保证修改后系统功能正常运行 - 使用VO替代实体类进行缓存,避免Hibernate代理对象序列化问题,彻底规避懒加载异常 ## 3. 疑问澄清 1. **数据转换问题**: 如何处理从实体类到VO的转换和从VO到实体类的转换? - 实体类应该实现Voable接口,提供toVo方法实现从实体类到VO的转换 - Service类实现VoableService接口提供updateByVo方法实现从Vo到实体类的转换,参考 ProjectQuotationService 的 updateByVo方法 2. **缓存键表达式**: 修改泛型后,缓存注解中的键表达式(如@CacheEvict(key = "#p0.id"))是否需要修改? - 需要确认VO类是否与实体类具有相同的属性结构 3. **依赖影响**: 修改Service泛型后,对调用这些Service的其他组件(如Controller、其他Service)会有什么影响? - 需要评估依赖影响范围并考虑如何处理 4. **事务处理**: 修改后事务处理是否会受到影响? - 需要确保事务边界正确维护 5. **查询规范**: getSpecification方法如何适配从Entity到Vo的转换? - 解决方案:Service类同时实现IEntityService<实体类>和QueryService接口 - 在IEntityService<实体类>接口中保持getSpecification方法返回基于JPA实体的Specification ```java public Page findAll(Specification spec, Pageable pageable) { return repository.findAll(spec, pageable); } ``` - 在QueryService接口的findAll方法中,先使用基于实体类的Specification查询数据,然后通过map操作将实体转换为Vo ```java @Override public Page findAll(JsonNode paramsNode, Pageable pageable) { Specification 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 page = findAll(spec, pageable); return page.map(ProjectCost::toVo); } ``` - 实体类实现Voable接口,提供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对象: - 服务类同时实现IEntityService<实体类>和QueryService接口 - QueryService接口的方法(如findById)添加@Cacheable注解 - 在这些缓存方法的实现中,查询实体后通过调用实体类的toVo()方法转换为Vo对象再返回 - 实体类实现Voable接口,提供toVo()方法进行转换 - 示例实现: ```java @Cacheable(key = "#id") @Override public CompanyFileTypeLocalVo findById(Integer id) { Optional optional = repository.findById(id); return optional.map(CompanyFileTypeLocal::toVo).orElse(null); } ``` 这种方式确保了缓存中存储的是转换后的Vo对象,而不是原始实体对象。 7. **WebSocket服务影响**: WebSocketServerCallbackManager类直接调用IEntityService接口,由于我们保持IEntityService泛型为实体类,因此对WebSocket服务的影响较小 - WebSocketServerCallbackManager类中的createNewEntity、findEntityTypeInInterfaces等方法主要依赖于IEntityService的实体类泛型 - 需要确认这些方法是否兼容当前的实现方式 8. **任务管理影响**: WebSocketServerTaskManager类中处理的任务主要依赖于IEntityService接口,由于我们保持了IEntityService的泛型为实体类,对任务管理的影响应该较小 ## 4. 初步决策 基于现有项目代码分析,做出以下初步决策: 1. **泛型修改策略**: 对于每个注解了@CacheConfig的Service类,同时实现IEntityService<实体类>和QueryService接口,保持IEntityService泛型为实体类,QueryService泛型为Vo类 2. **方法适配方案**: - IEntityService接口的方法保持基于实体类的实现 - QueryService接口的方法(如findById、findAll)在内部先查询实体,再通过实体类的toVo()方法转换为Vo对象返回 - 实体类实现Voable接口,提供toVo()方法进行转换 3. **缓存键处理**: QueryService接口的缓存方法(如findById)使用与实体类相同的ID属性作为缓存键,由于Vo类通常与实体类具有相同的ID属性,因此缓存注解可能不需要修改 4. **依赖处理**: 需要评估并处理所有调用修改后Service的组件,确保它们适应新的接口定义 5. **特殊方法处理**: 对于getSpecification等基于JPA实体的方法,保持在IEntityService接口中返回基于实体类的Specification 6. **缓存策略**: 确保所有标注@Cacheable的方法(主要是QueryService接口中的方法)都返回VO对象,并在存储前完成从实体类到VO的转换,以避免代理对象序列化问题 这些决策将在后续的共识和设计阶段进一步细化和确认。