# Server模块 Repository 实现经验总结 ## 1. Repository 基本架构 ### 1.1 接口设计原则 - Repository接口必须继承`MyRepository`基类 - 使用`@Repository`注解标记为Spring组件 - 遵循接口隔离原则,按业务功能组织方法 ### 1.2 包结构规范 ``` com.ecep.contract.ds.{business}.repository.{EntityName}Repository.java ``` 示例:`ContractBalanceRepository`位于`com.ecep.contract.ds.contract.repository` ## 2. 接口继承层次 ### 2.1 基础接口结构 ```java @Repository public interface ContractBalanceRepository extends MyRepository { // 自定义查询方法 } ``` ### 2.2 ⚠️ 重要错误示例 - 避免这些实现方式 **❌ 错误示例1:直接继承JpaRepository** ```java // ContractFileRepository, ContractItemRepository, VendorFileRepository, ProjectQuotationRepository @Repository public interface ContractFileRepository extends JpaRepository, JpaSpecificationExecutor { // 错误:不应该直接继承JpaRepository List findByContractId(Integer contractId); } ``` **❌ 错误示例2:继承多个冗余接口** ```java // PurchaseOrderRepository public interface PurchaseOrderRepository extends CrudRepository, PagingAndSortingRepository, JpaRepository, JpaSpecificationExecutor { // 错误:冗余接口继承,应该只继承MyRepository } ``` **❌ 错误示例3:缺少@Repository注解** ```java // CompanyFileRepository, ProjectFileRepository public interface CompanyFileRepository extends MyRepository { // 错误:缺少@Repository注解 List findByCompany(Company company); } ``` **❌ 错误示例4:缺少JavaDoc注释** ```java // 多个Repository存在此问题 public interface VendorFileRepository extends MyRepository { // 错误:缺少JavaDoc注释说明用途和方法 List findAllByVendorId(int vendorId); } ``` **✅ 正确实现示例** ```java /** * 合同Repository - 提供合同相关的数据库访问操作 */ @Repository public interface ContractRepository extends MyRepository { // 正确:统一继承MyRepository,有完整的JavaDoc /** * 根据合同代码查询合同 * @param code 合同代码 * @return 合同 Optional */ Optional findByCode(String code); /** * 根据状态查询合同列表 * @param status 合同状态 * @return 合同列表 */ List findByStatus(ContractStatus status); } ``` ### 2.3 当前项目错误统计 基于对全项目22个Repository的全面分析,发现以下问题分布: **🔴 继承层次错误(5个):** - `ContractFileRepository` - 直接继承JpaRepository - `ContractItemRepository` - 直接继承JpaRepository - `VendorFileRepository` - 直接继承JpaRepository - `ProjectQuotationRepository` - 直接继承JpaRepository - `PurchaseOrderRepository` - 继承4个冗余接口 **🟡 注解缺失(2个):** - `CompanyFileRepository` - 缺少@Repository注解 - `ProjectFileRepository` - 缺少@Repository注解 **🟡 文档注释不完整(4个):** - `CompanyFileRepository` - 缺少JavaDoc注释 - `ProjectFileRepository` - 缺少JavaDoc注释 - `VendorFileRepository` - 缺少JavaDoc注释 - `ProjectQuotationRepository` - 缺少JavaDoc注释 **✅ 正确实现(17个):** - contract: ContractRepository, ContractBalanceRepository, SalesOrderRepository, ContractTypeRepository, ContractKindRepository, ContractInvoiceRepository - company: CompanyRepository - customer: CompanyCustomerRepository, CustomerCatalogRepository - project: ProjectRepository - vendor: VendorRepository ### 2.5 MyRepository基类能力 `MyRepository`接口继承结构: - `JpaRepository`提供CRUD操作 - 继承`JpaSpecificationExecutor`提供条件查询能力 - 继承`QueryByExampleExecutor`提供示例查询能力 ## 3. 查询方法设计 ### 3.1 方法命名规范 使用Spring Data JPA的查询方法命名约定: #### 基本查询方法 - `findBy{属性名}` - 根据属性查找 - `findBy{属性名}And{属性名}` - 多条件AND查询 - `findBy{属性名}Or{属性名}` - 多条件OR查询 - `findBy{属性名}OrderBy{排序属性}` - 带排序查询 #### 关联对象查询 - `findBy{关联对象属性名}.{关联对象属性}` - 嵌套属性查询 - 支持多级嵌套,如:`findByContractCompanyName` #### 特殊查询类型 ```java // 根据业务ID查找 ContractBalance findByGuid(UUID guid); // 根据合同ID查找(带分页支持) Page findByContractId(Integer contractId, Pageable pageable); // 根据多个条件组合查询 List findByContractIdAndRefId(Integer contractId, String refId); ``` ### 3.2 自定义查询实现 当方法命名无法满足需求时,使用`@Query`注解: ```java @Query("SELECT cb FROM ContractBalance cb WHERE cb.contract.id = :contractId " + "AND cb.guid = :guid AND cb.setupDate >= :startDate") Page findByContractAndGuidAndDateRange( @Param("contractId") Integer contractId, @Param("guid") UUID guid, @Param("startDate") LocalDateTime startDate, Pageable pageable ); ``` ## 4. 分页和排序支持 ### 4.1 分页查询 ```java // 在Repository接口中定义 Page findByContractId(Integer contractId, Pageable pageable); // 在Service中调用 Page result = repository.findByContractId(contractId, PageRequest.of(page, size, Sort.by("setupDate").descending())); ``` ### 4.2 排序规范 - 默认按主键ID降序排列 - 业务相关字段按创建时间降序 - 支持多字段排序:`Sort.by("field1").ascending().and(Sort.by("field2").descending())` ## 5. 统计和聚合查询 ### 5.1 基础统计 ```java @Query("SELECT COUNT(cb) FROM ContractBalance cb WHERE cb.contract.id = :contractId") Long countByContractId(@Param("contractId") Integer contractId); @Query("SELECT SUM(cb.balanceAmount) FROM ContractBalance cb WHERE cb.contract.id = :contractId") BigDecimal sumBalanceByContractId(@Param("contractId") Integer contractId); ``` ### 5.2 复杂聚合 ```java @Query("SELECT new com.ecep.contract.vo.ContractBalanceStatsVO(" + "cb.contract.id, COUNT(cb), SUM(cb.balanceAmount)) " + "FROM ContractBalance cb GROUP BY cb.contract.id") List getBalanceStatsByContract(); ``` ## 6. 缓存策略 ### 6.1 缓存注解使用 虽然Repository本身不直接使用缓存,但需要考虑Service层的缓存需求: ```java // 在Service层配合使用 @Cacheable(key = "#p0") ContractBalance findById(Integer id); @CacheEvict(key = "#p0.id") ContractBalance save(ContractBalance contractBalance); ``` ### 6.2 缓存键设计原则 - 单一记录:使用主键作为键,如`"balance-" + id` - 按业务维度缓存:使用业务标识,如`"contract-" + contractId` - 避免缓存键冲突 ## 7. 事务管理 ### 7.1 事务传播级别 - 查询方法:默认`REQUIRED` - 写操作:明确指定`@Transactional(propagation = Propagation.REQUIRED)` ### 7.2 异常处理 ```java @Transactional public ContractBalance saveWithRetry(ContractBalance balance) { try { return repository.save(balance); } catch (DataIntegrityViolationException e) { // 处理数据完整性异常 throw new BusinessException("数据重复或违反约束", e); } } ``` ## 8. 性能优化 ### 8.1 懒加载优化 ```java // 在查询时指定抓取策略 @Query("SELECT cb FROM ContractBalance cb LEFT JOIN FETCH cb.contract " + "LEFT JOIN FETCH cb.employee WHERE cb.id = :id") Optional findByIdWithRelations(@Param("id") Integer id); ``` ### 8.2 批量操作优化 ```java // 批量插入 @Modifying @Query("DELETE FROM ContractBalance cb WHERE cb.contract.id IN :contractIds") void deleteByContractIds(@Param("contractIds") List contractIds); ``` ## 9. 错误处理和调试 ### 9.1 常见异常类型 - `DataAccessException` - 数据访问异常 - `DataIntegrityViolationException` - 数据完整性异常 - `EmptyResultDataAccessException` - 空结果异常 ### 9.2 日志记录 ```java @Repository @Slf4j public class ContractBalanceRepository { @Query("...") public List findComplexQuery(...) { log.debug("执行复杂查询: {}", jpql); // 执行查询 } } ``` ## 10. 测试策略 ### 10.1 Repository测试 ```java @DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ContractBalanceRepositoryTest { @Autowired private TestEntityManager em; @Autowired private ContractBalanceRepository repository; @Test void shouldFindByContractId() { // 测试数据准备 ContractBalance balance = createTestBalance(); // 执行测试 Page result = repository.findByContractId( balance.getContract().getId(), PageRequest.of(0, 10) ); // 断言验证 assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().get(0)).isEqualTo(balance); } } ``` ### 10.2 性能测试 ```java @Test @RepeatedTest(100) @Timeout(value = 5, unit = TimeUnit.SECONDS) void performanceTest() { // 性能测试实现 } ``` ## 11. 最佳实践总结 ### 11.1 设计原则 1. **单一职责**:每个Repository只负责一个实体类型 2. **接口隔离**:根据客户端需求设计专用查询方法 3. **命名清晰**:方法名应能直观表达查询意图 4. **性能优先**:合理使用索引和查询优化 ### 11.2 代码规范 1. **注解完整**:正确使用`@Repository`、`@Query`、`@Param` 2. **参数校验**:对传入参数进行null和有效性检查 3. **异常处理**:捕获并处理数据库访问异常 4. **日志记录**:记录关键查询操作和性能指标 ### 11.3 维护性 1. **版本兼容**:考虑数据库模式变更的影响 2. **向后兼容**:新增方法不影响现有功能 3. **文档完整**:复杂查询添加JavaDoc说明 4. **测试覆盖**:确保关键查询逻辑有测试覆盖 ## 12. 常见问题和解决方案 ### 12.1 N+1查询问题 **问题**:查询列表时触发N+1次关联查询 **解决**:使用`JOIN FETCH`或`@EntityGraph` ### 12.2 性能瓶颈 **问题**:复杂查询导致响应慢 **解决**: 1. 添加数据库索引 2. 优化查询语句 3. 使用分页限制结果集 4. 考虑使用缓存 ### 12.3 事务死锁 **问题**:并发操作导致事务死锁 **解决**: 1. 合理设计事务边界 2. 调整事务隔离级别 3. 实现重试机制 ## 13. 扩展建议 ### 13.1 动态查询支持 考虑使用`Querydsl`或`Specification`支持动态查询构建。 ### 13.2 读写分离 实现主从数据库分离,提升查询性能。 ### 13.3 分布式缓存 结合Redis等分布式缓存,提升查询性能。 --- ## 14. 实施检查清单 ### 14.1 Repository实现后验证步骤 在每个Repository实现完成后,请按以下清单进行验证: #### ✅ 基础架构检查 - [ ] Repository接口继承了 `MyRepository` - [ ] 使用了 `@Repository` 注解 - [ ] 包路径符合规范:`com.ecep.contract.ds.{business}.repository` - [ ] 类名符合命名规范:`{EntityName}Repository` #### ✅ 方法设计检查 - [ ] 遵循Spring Data JPA命名约定 - [ ] 包含必要的业务查询方法 - [ ] 复杂查询使用 `@Query` 注解 - [ ] 分页方法支持 `Pageable` 参数 #### ✅ 文档和注释检查 - [ ] 类级别JavaDoc注释完整 - [ ] 关键方法有JavaDoc说明 - [ ] 参数和返回值有明确说明 #### ✅ 性能和质量检查 - [ ] 避免N+1查询问题 - [ ] 适当使用索引提示 - [ ] 包含必要的单元测试 - [ ] 异常处理考虑周全 ### 14.2 常见错误检查 在提交代码前,特别检查是否犯了以下常见错误: #### ❌ 继承层次错误 ```java // 错误示例 - 不要这样做 public interface SomeRepository extends JpaRepository public interface AnotherRepository extends CrudRepository // 正确做法 - 统一使用MyRepository public interface SomeRepository extends MyRepository ``` #### ❌ 方法命名不规范 ```java // 错误示例 List getDataByCondition(String condition) // 使用get前缀而非find // 正确做法 List findByCondition(String condition) // 使用find前缀 ``` #### ❌ 缺少必要文档 ```java // 错误示例 - 无注释 List findByStatus(String status); // 正确做法 - 完整注释 /** * 根据状态查询实体列表 * * @param status 状态值 * @return 符合状态的实体列表 */ List findByStatus(String status); ``` ### 14.3 架构一致性验证 确保新实现的Repository与项目中其他Repository保持一致: 1. **继承模式统一**:所有Repository都必须继承MyRepository 2. **注解使用统一**:统一使用@Repository注解 3. **命名约定统一**:遵循Spring Data JPA命名规范 4. **文档风格统一**:保持JavaDoc注释风格一致 ### 14.4 后续维护注意事项 - 新增查询方法时遵循现有命名规范 - 修改现有方法时保持向后兼容性 - 定期审查Repository方法的性能和合理性 - 及时更新相关文档和测试用例 --- *本文档基于ContractBalanceRepository实现经验总结,结合项目实际情况分析,其他Repository实现应参考此文档规范。特别注意避免文档中提到的常见错误。*