H2在springboot的单元测试中的应用

发布于:2025-07-13 ⋅ 阅读:(20) ⋅ 点赞:(0)

为什么要使用H2做单元测试

1. 内存模式运行(零配置、零残留)

  • 无需安装:只需添加依赖即可使用,不依赖外部服务
  • 内存数据库:通过 jdbc:h2:mem:testdb 配置,数据完全存在于内存中
  • 测试后自动销毁:进程结束即消失,不会残留测试数据污染环境
  • 示例
    spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
    

2. 极速启动与执行

  • 毫秒级启动:比 MySQL/PostgreSQL 等传统数据库快 10-100 倍
  • 高性能:内存操作避免磁盘 I/O 瓶颈,加速测试执行
  • 统计对比
    数据库 启动时间 简单查询延迟
    H2 50ms 0.1ms
    MySQL 2s+ 1-10ms

3. 隔离性与可重复性

  • 独立实例:每个测试用例可创建独立数据库实例
  • 事务支持:配合 @Transactional 实现自动回滚
  • 代码示例
    @Test
    @Transactional // 测试后自动回滚
    public void testInsert() {
        repository.save(new Entity());
        assertEquals(1, repository.count()); // 断言生效
    } // 此处自动回滚,不影响其他测试
    

4. 兼容性强大

  • 多模式支持:可模拟其他数据库行为
    jdbc:h2:mem:test;MODE=MySQL # 模拟MySQL语法
    
  • 兼容常见SQL:支持标准 SQL、存储过程、触发器等功能
  • 规避问题:测试时暴露 SQL 兼容性问题,避免生产环境踩坑

5. 开发体验优化

  • 嵌入式控制台:通过 Web 界面实时查看测试数据
    spring.h2.console.enabled=true
    spring.h2.console.path=/h2-console
    
  • 自动初始化:配合 schema.sql/data.sql 快速构建测试场景
  • 与框架深度集成
    • Spring Boot 自动配置
    • MyBatis/JPA 无缝衔接
    • Testcontainers 兼容

6. CI/CD 友好

  • 无外部依赖:适合 Docker/CI 环境(如 GitHub Actions)
  • 资源占用低:仅需 2MB JAR 文件,不消耗额外内存
  • 并行测试:支持多线程同时运行测试类

对比传统数据库的劣势

场景 H2 表现 生产数据库表现
复杂查询优化 无查询优化器 有优化器
大数据量测试 内存限制(约 1GB 数据) 支持 TB 级数据
数据库特性测试 部分语法不兼容 完全兼容

何时适合用 H2?

  • 需要测试数据库专属特性(如 Oracle 的窗口函数)
  • 需要验证超大数据量性能
  • 测试特定数据库的兼容性(此时建议用 Testcontainers + 真实数据库)

最佳实践建议

  1. 基础测试:95% 的 CRUD/业务逻辑测试用 H2
  2. 集成测试:结合 @Testcontainers 补充真实数据库测试
  3. 配置示例
    # src/test/resources/application-test.yml
    spring:
      datasource:
        url: jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1
        username: sa
        password: ""
    

H2 通过极简的设计实现了单元测试的核心需求——快速、隔离、可重复,这正是它成为 Java 单元测试首选数据库的原因。

具体实践

首先看我的项目结构,非常的简单,而且大部分都是使用 mybatis-plus 的插件自动生成的

在这里插入图片描述
在这里插入图片描述
这里要说明几点:

  1. test/resources 中的 application配置文件如果存在的话,那么运行单元测试的时候是会首先加载这个配置文件的,而且这个配置文件在正常启动项目的时候,也不会被加载,所以,放心大胆的使用
  2. schema.sql 和 data.sql 文件:首先我们要知道 h2 数据库是内存数据库,也就是说,保留不下来表结构这些东西,不像mysql一样会将数据放到磁盘里面,所以每次我们使用 h2 的时候,都要重新的 建个表,加载一些初始数据

test下的配置文件如下

spring:
  datasource:
    url: spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;IFEXISTS=TRUE
    driver-class-name: org.h2.Driver
    username: sa
    password: ""
    # 初始化数据库脚本
    schema: classpath:schema.sql
    data: classpath:data.sql
    initialization-mode: embedded
  h2:
    console:
      enabled: true
      path: /h2-console

# MyBatis-Plus 配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 输出SQL到控制台
  global-config:
    db-config:
      id-type: auto # 主键策略
  mapper-locations: classpath*:mapper/**/*.xml

pom 文件如下,pom文件不区分 测试和正式

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--        mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

<!--        h2 数据库-->
        <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <scope>test</scope>
        </dependency>

<!--        mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version> <!-- 请根据需要选择合适的版本 -->
        </dependency>

单元测试的书写

@SpringBootTest
@Transactional // 测试后自动回滚
public class StudentMapperTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    public void testSelectAll() {
        List<Student> students = studentMapper.selectList(null);
        Assertions.assertEquals(3, students.size()); // 验证data.sql中的数据
    }

    @Test
    public void testInsert() {
        Student newStudent = new Student();
        newStudent.setName("赵六");

        int result = studentMapper.insert(newStudent);
        Assertions.assertEquals(1, result);

        Student dbStudent = studentMapper.selectById(newStudent.getId());
        Assertions.assertEquals("赵六", dbStudent.getName());
    }
}

以上代码经过实地的验证,可以运行


网站公告

今日签到

点亮在社区的每一天
去签到