# Server模块 Repository 实现经验总结

## 1. Repository 基本架构

### 1.1 接口设计原则
- Repository接口必须继承`MyRepository<T, ID>`基类
- 使用`@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<ContractBalance, Integer> {
    // 自定义查询方法
}
```

### 2.2 ⚠️ 重要错误示例 - 避免这些实现方式

**❌ 错误示例1：直接继承JpaRepository**
```java
// ContractFileRepository, ContractItemRepository, VendorFileRepository, ProjectQuotationRepository
@Repository
public interface ContractFileRepository 
    extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
    // 错误：不应该直接继承JpaRepository
    List<ContractFile> findByContractId(Integer contractId);
}
```

**❌ 错误示例2：继承多个冗余接口**
```java
// PurchaseOrderRepository
public interface PurchaseOrderRepository extends
        CrudRepository<PurchaseOrder, Integer>, 
        PagingAndSortingRepository<PurchaseOrder, Integer>, 
        JpaRepository<PurchaseOrder, Integer>, 
        JpaSpecificationExecutor<PurchaseOrder> {
    // 错误：冗余接口继承，应该只继承MyRepository
}
```

**❌ 错误示例3：缺少@Repository注解**
```java
// CompanyFileRepository, ProjectFileRepository
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
    // 错误：缺少@Repository注解
    List<CompanyFile> findByCompany(Company company);
}
```

**❌ 错误示例4：缺少JavaDoc注释**
```java
// 多个Repository存在此问题
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
    // 错误：缺少JavaDoc注释说明用途和方法
    List<VendorFile> findAllByVendorId(int vendorId);
}
```

**✅ 正确实现示例**
```java
/**
 * 合同Repository - 提供合同相关的数据库访问操作
 */
@Repository
public interface ContractRepository extends MyRepository<Contract, Integer> {
    // 正确：统一继承MyRepository，有完整的JavaDoc
    
    /**
     * 根据合同代码查询合同
     * @param code 合同代码
     * @return 合同 Optional
     */
    Optional<Contract> findByCode(String code);
    
    /**
     * 根据状态查询合同列表
     * @param status 合同状态
     * @return 合同列表
     */
    List<Contract> 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<ContractBalance> findByContractId(Integer contractId, Pageable pageable);

// 根据多个条件组合查询
List<ContractBalance> 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<ContractBalance> findByContractAndGuidAndDateRange(
    @Param("contractId") Integer contractId,
    @Param("guid") UUID guid,
    @Param("startDate") LocalDateTime startDate,
    Pageable pageable
);
```

## 4. 分页和排序支持

### 4.1 分页查询
```java
// 在Repository接口中定义
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);

// 在Service中调用
Page<ContractBalance> 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<ContractBalanceStatsVO> 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<ContractBalance> 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<Integer> contractIds);
```

## 9. 错误处理和调试

### 9.1 常见异常类型
- `DataAccessException` - 数据访问异常
- `DataIntegrityViolationException` - 数据完整性异常
- `EmptyResultDataAccessException` - 空结果异常

### 9.2 日志记录
```java
@Repository
@Slf4j
public class ContractBalanceRepository {
    
    @Query("...")
    public List<ContractBalance> 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<ContractBalance> 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<T, ID>`
- [ ] 使用了 `@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<Entity, Integer>
public interface AnotherRepository extends CrudRepository<Entity, Integer>

// 正确做法 - 统一使用MyRepository
public interface SomeRepository extends MyRepository<Entity, Integer>
```

#### ❌ 方法命名不规范
```java
// 错误示例
List<Entity> getDataByCondition(String condition)  // 使用get前缀而非find

// 正确做法
List<Entity> findByCondition(String condition)     // 使用find前缀
```

#### ❌ 缺少必要文档
```java
// 错误示例 - 无注释
List<Entity> findByStatus(String status);

// 正确做法 - 完整注释
/**
 * 根据状态查询实体列表
 * 
 * @param status 状态值
 * @return 符合状态的实体列表
 */
List<Entity> findByStatus(String status);
```

### 14.3 架构一致性验证
确保新实现的Repository与项目中其他Repository保持一致：

1. **继承模式统一**：所有Repository都必须继承MyRepository
2. **注解使用统一**：统一使用@Repository注解
3. **命名约定统一**：遵循Spring Data JPA命名规范
4. **文档风格统一**：保持JavaDoc注释风格一致

### 14.4 后续维护注意事项
- 新增查询方法时遵循现有命名规范
- 修改现有方法时保持向后兼容性
- 定期审查Repository方法的性能和合理性
- 及时更新相关文档和测试用例

---

*本文档基于ContractBalanceRepository实现经验总结，结合项目实际情况分析，其他Repository实现应参考此文档规范。特别注意避免文档中提到的常见错误。*