Spring JPA含嵌入式主键的实体无法删除问题排查
问题现象
使用Spring JPA管理带有@EmbeddedId复合主键的TestCaseExecutionResult实体时,调用删除接口后实体仍可被查询到,SQL日志中无删除语句输出。
关键问题排查与解决方案
1. 嵌入式主键类的equals和hashCode实现错误
JPA依赖主键类的equals和hashCode方法识别实体唯一性,若这两个方法未正确实现,deleteById会无法匹配到目标实体,导致删除操作不执行。
解决:确保TestCaseExecutionResultPK正确实现equals和hashCode
@Embeddable public class TestCaseExecutionResultPK implements Serializable { private Integer id; private Integer testCaseResultId; private Integer testCaseId; private Integer testRegionId; // Getters、Setters、构造方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TestCaseExecutionResultPK that = (TestCaseExecutionResultPK) o; return Objects.equals(id, that.id) && Objects.equals(testCaseResultId, that.testCaseResultId) && Objects.equals(testCaseId, that.testCaseId) && Objects.equals(testRegionId, that.testRegionId); } @Override public int hashCode() { return Objects.hash(id, testCaseResultId, testCaseId, testRegionId); } }
2. 删除接口的路径变量顺序不匹配
控制器删除方法的参数顺序与测试中传递的路径变量顺序可能不一致,导致构造的主键错误,无法找到目标实体。
解决:校验路径模板与参数顺序
确认Endpoints.EXECUTION_RESULT_REGION_EXECUTION的路径模板(如/testcases/{tcId}/regions/{regionId}/results/{resultId}/executions/{executionId})与控制器参数顺序tcId, regionId, resultId, executionId完全对应,测试中传递参数时保持相同顺序:
// 测试中调用删除接口时参数顺序需与控制器一致 this.restTemplate.delete( "http://localhost:" + port + Endpoints.EXECUTION_RESULT_REGION_EXECUTION, testCaseExecutionResult.getTestCaseExecutionResultPK().getTestCaseId(), // tcId testCaseExecutionResult.getTestCaseExecutionResultPK().getTestRegionId(), // regionId testCaseExecutionResult.getTestCaseExecutionResultPK().getTestCaseResultId(), // resultId testCaseExecutionResult.getTestCaseExecutionResultPK().getId() // executionId );
3. 缓存未失效导致查询旧数据
实体类添加了@Cacheable注解,删除操作后缓存未被清除,查询时直接返回缓存中的旧实体。
解决:删除时清除对应缓存
在Repository的deleteById方法上添加@CacheEvict注解:
public interface TestCaseExecutionResultRepository extends JpaRepository<TestCaseExecutionResult, TestCaseExecutionResultPK> { // 其他自定义方法... @CacheEvict(value = "testCaseExecutionResult", key = "#p0") @Override void deleteById(TestCaseExecutionResultPK id); }
或在控制器删除方法上添加@CacheEvict:
@DeleteMapping(Endpoints.EXECUTION_RESULT_REGION_EXECUTION) @CacheEvict(value = "testCaseExecutionResult", key = "#pk") public ResponseEntity<?> delete( @PathVariable Integer tcId, @PathVariable Integer regionId, @PathVariable Integer resultId, @PathVariable Integer executionId) throws NotFoundException { TestCaseExecutionResultPK pk = new TestCaseExecutionResultPK(executionId, resultId, tcId, regionId); // ...删除逻辑 }
4. 事务未正确提交
虽然Spring Data JPA默认会为deleteById添加事务,但如果控制器方法未被Spring事务管理覆盖,可能导致操作未提交。
解决:显式添加事务注解并简化删除逻辑
在控制器删除方法上添加@Transactional,同时用existsById替代findAllById,避免不必要的实体查询:
@DeleteMapping(Endpoints.EXECUTION_RESULT_REGION_EXECUTION) @Transactional public ResponseEntity<?> delete( @PathVariable Integer tcId, @PathVariable Integer regionId, @PathVariable Integer resultId, @PathVariable Integer executionId) throws NotFoundException { TestCaseExecutionResultPK pk = new TestCaseExecutionResultPK(executionId, resultId, tcId, regionId); if (repository.existsById(pk)) { repository.deleteById(pk); return ResponseEntity.noContent().build(); } else { throw new NotFoundException("Test case execution result with id " + pk + " not found"); } }
5. 自定义查询方法逻辑错误
测试中删除后调用的executionResultsForTestCaseInRegion接口依赖自定义查询,需确认Repository中的方法是否正确关联主键字段:
// 确保Repository中的方法正确匹配主键字段 List<TestCaseExecutionResult> findByTestCaseExecutionResultPK_TestCaseIdAndTestCaseExecutionResultPK_TestRegionId(Integer tcId, Integer regionId);
或检查NamedQuery的JPQL是否正确关联主键属性。
内容的提问来源于stack exchange,提问作者javydreamercsw

