JUnit5 测试中 H2 内存数据库主键 ID 冲突与实体关联失效问题解析

酷杰吖_3522

酷杰吖_3522

2026-06-15

389人浏览

原创

本文详解 Spring Boot + JUnit5 + H2 测试环境下,因手动设置自增主键 ID 导致 EntityNotFoundException 的根本原因,并提供安全、可复用的测试数据初始化方案。

本文详解 spring boot + junit5 + h2 测试环境下,因手动设置自增主键 id 导致 `entitynotfoundexception` 的根本原因,并提供安全、可复用的测试数据初始化方案。

在使用 @DataJpaTest 配合 H2 内存数据库进行 Repository 层单元测试时,一个常见但极易被忽视的问题是:手动为 @GeneratedValue(strategy = GenerationType.IDENTITY) 字段赋值(如 domain.setId(1))会导致数据库实际生成的 ID 与 Java 对象中持有的 ID 不一致,进而引发级联操作失败或 EntityNotFoundException。

从你提供的日志中可清晰看到关键线索:

Hibernate: insert into domain (id, name, uid) values (default, ?, ?)
...
Unable to find com.testiqo.exam.engine.entity.Domain with id 1

H2 正确执行了 INSERT INTO domain (...) VALUES (default, ...) —— 即让数据库自动生成 ID(例如 1、2 等),但你的 Domain 实体对象中 id=1 是硬编码的。当 Topic 关联该 Domain 并调用 topicRepository.save(topic) 时,JPA 尝试根据 topic.getDomain().getId()(即 1)去加载 Domain,而此时若该 ID 尚未被持久化(或已被覆盖/冲突),就会抛出 EntityNotFoundException。

文涌Effidit
文涌Effidit

文涌Effidit是一款AI文本写作工具,Tencent AI Lab 开发的智能创作助手。

下载

✅ 正确做法:让数据库生成 ID,再回填实体

@BeforeEach 方法应负责完整生命周期管理:创建 → 持久化 → 获取真实 ID → 构建依赖关系。避免在测试方法内重复 save(),更不可在 createTopic() 中新建 Domain(造成对象不一致)。

以下是重构后的推荐写法:

@DataJpaTest
@ActiveProfiles(Constant.TEST_PROFILE)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 显式禁用自动配置,确保使用 H2
class TopicRepositoryTest {

    @Autowired
    private TestEntityManager entityManager; // 推荐使用,比直接 save() 更可控

    @Autowired
    private TopicRepository topicRepository;

    @Autowired
    private DomainRepository domainRepository;

    private Domain domain;
    private Topic topic;

    @BeforeEach
    void setUp() {
        // 1. 创建并持久化 Domain,获取数据库分配的真实 ID
        domain = createDomain();
        domain = domainRepository.save(domain); // ← 关键:保存后 domain.getId() 已更新为 DB 生成值
        System.out.println("Saved Domain ID: " + domain.getId());

        // 2. 创建 Topic,关联已持久化的 domain 实例(非新构造对象)
        topic = createTopic(domain);
        topic = topicRepository.save(topic);
        System.out.println("Saved Topic ID: " + topic.getId());
    }

    private Domain createDomain() {
        Domain d = new Domain();
        d.setName("Java");
        d.setUid("1234z637-c6c1-4dc3-9755-9f6ad974d70b");
        return d;
    }

    private Topic createTopic(Domain domain) {
        Topic t = new Topic();
        t.setName("Java");
        t.setUid("35f66eaa-2fc7-4170-8470-feb83ddbf6b0");
        t.setDomain(domain); // ← 直接引用已保存的 domain 实例
        return t;
    }

    @Test
    void findAllTopicByDomainId() {
        // domain 和 topic 已在 @BeforeEach 中保存,无需重复 save
        List<topic> result = topicRepository.findAllTopicByDomainId(domain.getUid());
        assertThat(result).hasSize(1)
                          .extracting("name").containsExactly("Java");
    }

    @Test
    void deleteByUid() {
        assertTrue(topicRepository.findById(topic.getId()).isPresent());
        topicRepository.deleteByUid(topic.getUid());
        assertFalse(topicRepository.findById(topic.getId()).isPresent());
    }

    @Test
    void getTopicByUid() {
        Topic result = topicRepository.getTopicByUid(topic.getUid());
        assertEquals("Java", result.getName());
        assertEquals(domain.getId(), result.getDomain().getId()); // 验证关联完整性
    }
}</topic>

⚠️ 关键注意事项

  • 移除 @ExtendWith(MockitoExtension.class):@DataJpaTest 是集成测试注解,用于启动 JPA 上下文,与 Mockito 模拟无直接关系;混用可能导致上下文冲突,且 @MockBean 才是 JPA 测试中模拟依赖的正确方式(本例无需)。
  • @AutoConfigureTestDatabase(replace = Replace.NONE) 已过时:Spring Boot 3+ 应使用 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE),确保 H2 配置生效。
  • 避免手动设 ID:所有 @GeneratedValue 字段(如 id)在测试中绝不应手动赋值,全部交由数据库生成。
  • 优先使用 TestEntityManager:它提供 persistAndFlush()、find() 等精确控制方法,比直接调用 Repository 更适合验证底层行为。
  • 验证关联完整性:如 getTopicByUid() 测试中检查 result.getDomain().getId(),确保外键关系正确建立。

遵循以上实践,即可彻底规避“ID 不匹配”导致的 JpaObjectRetrievalFailureException,写出稳定、可维护、符合 JPA 语义的 Repository 单元测试。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
软件测试常用工具
软件测试常用工具

软件测试常用工具有Selenium、JUnit、Appium、JMeter、LoadRunner、Postman、TestNG、LoadUI、SoapUI、Cucumber和Robot Framework等等。测试人员可以根据具体的测试需求和技术栈选择适合的工具,提高测试效率和准确性 。

2023.10.13

4210

8

java测试工具有哪些
java测试工具有哪些

java测试工具有JUnit、TestNG、Mockito、Selenium、Apache JMeter和Cucumber。php还给大家带来了java有关的教程,欢迎大家前来学习阅读,希望对大家能有所帮助。

2023.10.23

2601

10

Java 单元测试
Java 单元测试

本专题聚焦 Java 在软件测试与持续集成流程中的实战应用,系统讲解 JUnit 单元测试框架、Mock 数据、集成测试、代码覆盖率分析、Maven 测试配置、CI/CD 流水线搭建(Jenkins、GitHub Actions)等关键内容。通过实战案例(如企业级项目自动化测试、持续交付流程搭建),帮助学习者掌握 Java 项目质量保障与自动化交付的完整体系。

2025.10.24

1006

26

LLVM自定义Pass怎么写
LLVM自定义Pass怎么写

本专题聚焦LLVM自定义Pass开发,整理Pass类结构、run()方法、PreservedAnalyses、CMake构建、插件注册、-load-pass-plugin加载和测试用例编写流程。

2026.09.30

0

10

LLVM RISC-V参数配置教程
LLVM RISC-V参数配置教程

本专题介绍LLVM对RISC-V基础ISA和扩展的支持方式,涵盖RV32、RV64、标准扩展、实验性扩展、厂商扩展、-menable-experimental-extensions和版本差异。

2026.09.30

0

14

LLVM IR中间表示入门指南
LLVM IR中间表示入门指南

本专题整理LLVM IR的核心概念,包括中间表示作用、模块结构、函数、基本块、SSA形式、类型系统和常见语法,帮助新手理解LLVM编译流程中的关键层。

2026.09.30

0

12

PDF转图片方法
PDF转图片方法

需要把 PDF 页面用于上传、预览、分享或图片归档时,PDF 转图片方法专题整理 JPG/PNG 格式选择、逐页导出、清晰度设置、批量下载和结果检查等流程,帮助用户稳定完成 PDF 图片化处理。

2026.09.30

0

26

PixTV AI视频生成与无限画布创作
PixTV AI视频生成与无限画布创作

PixTV专题整理AI视频与视觉内容创作相关功能使用教程,涵盖AI生图、视频生成、无限画布、多模型创作、素材管理、声音音乐及视频剪辑等功能,帮助用户快速掌握PixTV从创意到成片的完整制作方法。

2026.09.29

0

15

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

2026.09.23

220

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习