处理 Spring Boot H2 Exception:“Schema not found”

1、概览

H2 是一个开源的 SQL 数据库,在 Java 中通常用于测试。它是一个内存数据库,不会将任何数据持久化到磁盘,因此速度非常快。

在与 Spring Boot 整合时,我们可能会遇到 “Schema not found” 异常,本文将带你了解出现此异常的原因,以及如何解决该异常。

2、理解异常的原因

H2 的默认 Schema 是 PUBLIC。如果我们映射的 JPA 实体类不使用 PUBLIC Schema,则必须确保在 H2 上创建了 Schema。当目标 Schema 不存在时,Spring Boot 会抛出异常 “Schema not found”。

模拟一下这个场景。在 Spring Boot 应用中创建以下实体类和 Repository。

@Entity
@Table(name = "student", schema = "test")
public class Student {
    @Id
    @Column(name = "student_id", length = 10)
    private String studentId;

    @Column(name = "name", length = 100)
    private String name;

    // 构造函数、Getter\Setter 方法
}

如上 @Table 注解指定了实体映射到 test Schema 下的 student 表的映射细节。

public interface StudentRepository extends JpaRepository<Student, String> {
}

接下来,启动 Spring Boot 应用并访问 Repository。此时,会抛出异常,表示 Schema 不存在。

通过集成测试来验证这一点:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SampleSchemaApplication.class)
class SampleSchemaApplicationIntegrationTest {
    @Autowired
    private StudentRepository studentRepository;

    @Test
    void whenSaveStudent_thenThrowsException() {
        Student student = Student.builder()
          .studentId("24567433")
          .name("David Lloyds")
          .build();

        assertThatThrownBy(() -> studentRepository.save(student))
          .isInstanceOf(InvalidDataAccessResourceUsageException.class);
    }
}

执行测试时,我们可以在控制台中看到以下异常信息:

org.hibernate.exception.SQLGrammarException: could not prepare statement [Schema "TEST" not found; SQL statement:
select s1_0.student_id,s1_0.name from test.student s1_0 where s1_0.student_id=? [90079-214]] [select s1_0.student_id,s1_0.name from test.student s1_0 where s1_0.student_id=?]

3、通过数据库 URL 创建 Schema

要解决这个问题,必须在 Spring Boot 应用启动时创建相应的 Schema。有两种不同的方法。

第一种方法是在建立数据库连接时创建数据库 Schema。H2 数据库 URL 允许我们在客户端通过 INIT 属性指定在连接数据库时执行的 DDLDML 命令。在 Spring Boot 应用中,我们可以在 application.yaml 中定义 spring.datasource.url 属性:

spring:
  jpa:
    hibernate:
      ddl-auto: create
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:test;INIT=CREATE SCHEMA IF NOT EXISTS test

如果 Schema 不存在,初始化 DDL 会创建 Schema。需要注意的是,这种方法是 H2 数据库的专用方法,可能不适用于其他数据库。

这种方法通过数据库 URL 创建 Schema,而无需显式创建数据表。

我们依靠 Hibernate 的 ddl-auto 自动创建 Schema,通过在 YAML 文件中设置 spring.jpa.hibernate.ddl-auto 属性为 create

4、通过初始化脚本创建 Schema

第二种方法是通用的,也适用于其他数据库。通过初始化脚本创建所有数据库组件,包括 Schema 和数据表。

Spring Boot 会在执行初始化脚本之前初始化 JPA Persistence Unit(持久化单元)。因此,需要在 application.yaml 中显式禁用 Hibernate 的 ddl-auto 功能,因为初始化脚本会负责这个任务:

spring:
  jpa:
    hibernate:
      ddl-auto: none

如果不禁用它,就会在应用启动时遇到 “Schema TEST not found” 的异常,因为在 JPA Persistence Unit 初始化过程中,Schema 尚未创建。

现在,将创建 test Schema 和 student 表的 schema.sql 文件放到 resources 文件夹中:

CREATE SCHEMA IF NOT EXISTS test;

CREATE TABLE test.student (
  student_id VARCHAR(10) PRIMARY KEY,
  name VARCHAR(100)
);

默认情况下,Spring Boot 会在应用启动时查找 resources 文件夹中的 schema.sql DDL 脚本,以初始化数据库。

5、总结

“Schema not found” 异常是 Spring Boot 整合 H2 中常见的一个异常,我们可以通过数据库 URL 配置或初始化脚本来避免该异常。


Ref:https://www.baeldung.com/spring-boot-h2-exception-schema-not-found