Files
contract-manager/.trae/rules/server_repository_rules.md
songqq 02afa189f8 feat(contract): 新增合同余额功能及重构文件管理
重构合同文件管理逻辑,增加错误处理和日志记录
新增ContractBalance实体、Repository和VO类
完善Voable接口文档和实现规范
更新项目架构文档和数据库设计
修复SmbFileService的连接问题
移动合同相关TabSkin类到contract包
添加合同文件重建任务的WebSocket支持
2025-11-19 00:50:16 +08:00

14 KiB
Raw Blame History

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 基础接口结构

@Repository
public interface ContractBalanceRepository extends MyRepository<ContractBalance, Integer> {
    // 自定义查询方法
}

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

错误示例1直接继承JpaRepository

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

错误示例2继承多个冗余接口

// PurchaseOrderRepository
public interface PurchaseOrderRepository extends
        CrudRepository<PurchaseOrder, Integer>, 
        PagingAndSortingRepository<PurchaseOrder, Integer>, 
        JpaRepository<PurchaseOrder, Integer>, 
        JpaSpecificationExecutor<PurchaseOrder> {
    // 错误冗余接口继承应该只继承MyRepository
}

错误示例3缺少@Repository注解

// CompanyFileRepository, ProjectFileRepository
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
    // 错误:缺少@Repository注解
    List<CompanyFile> findByCompany(Company company);
}

错误示例4缺少JavaDoc注释

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

正确实现示例

/**
 * 合同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

特殊查询类型

// 根据业务ID查找
ContractBalance findByGuid(UUID guid);

// 根据合同ID查找带分页支持
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);

// 根据多个条件组合查询
List<ContractBalance> findByContractIdAndRefId(Integer contractId, String refId);

3.2 自定义查询实现

当方法命名无法满足需求时,使用@Query注解:

@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 分页查询

// 在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 基础统计

@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 复杂聚合

@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层的缓存需求

// 在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 异常处理

@Transactional
public ContractBalance saveWithRetry(ContractBalance balance) {
    try {
        return repository.save(balance);
    } catch (DataIntegrityViolationException e) {
        // 处理数据完整性异常
        throw new BusinessException("数据重复或违反约束", e);
    }
}

8. 性能优化

8.1 懒加载优化

// 在查询时指定抓取策略
@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 批量操作优化

// 批量插入
@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 日志记录

@Repository
@Slf4j
public class ContractBalanceRepository {
    
    @Query("...")
    public List<ContractBalance> findComplexQuery(...) {
        log.debug("执行复杂查询: {}", jpql);
        // 执行查询
    }
}

10. 测试策略

10.1 Repository测试

@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 性能测试

@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 动态查询支持

考虑使用QuerydslSpecification支持动态查询构建。

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 常见错误检查

在提交代码前,特别检查是否犯了以下常见错误:

继承层次错误

// 错误示例 - 不要这样做
public interface SomeRepository extends JpaRepository<Entity, Integer>
public interface AnotherRepository extends CrudRepository<Entity, Integer>

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

方法命名不规范

// 错误示例
List<Entity> getDataByCondition(String condition)  // 使用get前缀而非find

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

缺少必要文档

// 错误示例 - 无注释
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实现应参考此文档规范。特别注意避免文档中提到的常见错误。