跳到主要内容

数据库验证

功能介绍

诚然,Jimmer的强类型SQL DSL能在编译时尽可能暴露问题,大部分错误能够在测试之前就被杜绝。但,这是不够的。

强类型SQL DSL仅仅能提高实体模型->业务代码环节的可靠性,但无法保证数据库结构->实体模型环节的可靠性。

还有一个衍生问题,如果开发人员在测试环境中得到可靠的应用,那么,如何保证应用被移植到生产环境中后仍然可靠?

数据库验证是一个非常重要的功能,它用于验证数据库结构和实体模型定义之间的一致性。

信息

验证规则

  • 验证表名、列名、序列名是否和实体模型中的定义一致。

  • 验证列的nullity是否和实体模型中属性的定义一致。

    如果属性被@OneToOne(inputNotNull = true)@ManyToOne(inputNotNull = true)修饰,则无视属性本身的nullity,认为数据中对应的外键不能为null。

  • 对于实体模型中各实体的id属性,验证数据库是否有存在完全匹配的的主键约束。

  • 对于实体模型中定义的真外键 (参见真假外键) 而言,验证数据库中是否存在完全匹配的的外键约束。

开启验证

有两种方法开始验证

  • 使用Spring Boot Starter

    修改application.yml (或application.properties)

    jimmer:
    database-validation-mode: ERROR
  • 使用底层API

    JSqlClient sqlClient = JSqlClient
    .newBuilder()
    .setDatabaseValidationMode(
    DatabaseValidationMode.ERROR
    )
    ...省略其他配置...
    .build();

DatabaseValidationMode是枚举类型,有三个取值:

  • NONE: 不验证数据库结构,这是默认行为。

  • WARNING: 验证数据库结构,如果数据库结构和实体模型定义不一致,不阻止程序运行,只是在日志中打印告警信息。

  • ERROR: 验证数据库结构,如果数据库结构和实体模型定义不一致,抛出异常,阻止程序运行。

解决表冲突

  • 实体类型必然对应于数据库的某张表,无论是由开发人员通过@Table显式配置表名还是由命名策略自动决定。

  • 基于中间表的关联属性必然对应于数据库的某张表,无论是由开发人员通过@JoinTable显式配置表名还是由命名策略自动决定。

  • 自动增长策略为SEQUENCE的ID必然对应于数据库的某个序列,无论是由开发人员通过@GeneratedValue显式配置序列名还是由命名策略自动决定

程序启动时,Jimmer需要查询数据库以验证这些表或序列是否存在。以及,如果存在,其内部结构是否正确 (针对表)

然而,由于数据库服务往往支持多个子库,子库之间还可以彼此授权,所以JDBC连接可能在不同的数据库用户看到同名的表,比如,db1.BOOKdb2.BOOKdb3.BOOK。这叫表冲突。

当然,Jimmer可以优先从JDBC连接的元数据中提取数据库名称,比如,从jdbc:mysql://localhost:3306/db1中可以提取到"db1",优先在自动提取到的子库中查找表信息用于验证。 然而,该方法并不总是有效,只要当前子库中无法查找到指定的表,就会到其他子库中查找,这就可能导致表冲突问题。

要解决此问题,以下两种方法可供选择:

  • 明确指定严格的表名或序列名,例如:

    • @Table(name = "BOOK")修改为@Table(name = "db1.BOOK")

    • @JoinTable(name = "BOOK_AUTHOR_MAPPING")修改为@JoinTable(name = "db1.BOOK_AUTHOR_MAPPING")

    • @GeneratedValue(sequenceName = "BOOK_ID_SEQ")修改为@GeneratedValue(sequenceName = "db1.BOOK_ID_SEQ")

  • 明确指定被用于验证的子库名,这又可分为两种方法:

    有两种方法开始验证

    • 使用Spring Boot Starter

      修改application.yml (或application.properties)

      jimmer:
      database-validation:
      mode: ERROR
      catalog: db1
      信息

      jimmer.database-validation-modejimmer.database-validation.mode等价

    • 使用底层API

      JSqlClient sqlClient = JSqlClient
      .newBuilder()
      .setDatabaseValidationMode(
      DatabaseValidationMode.ERROR
      )
      .setDatabaseValidationCatalog("db1")
      ...省略其他配置...
      .build();

同理,也可以指定schema,例如

  • 直接配置:@Table(name = "mydatabase.myschema.BOOK")

  • SpringBoot的application.yml文件配置

    jimmer:
    database-validation:
    mode: ERROR
    catalog: mydatabase
    schema: myschema
  • 底层API配置

    JSqlClient sqlClient = JSqlClient
    .newBuilder()
    .setDatabaseValidationMode(
    DatabaseValidationMode.ERROR
    )
    .setDatabaseValidationCatalog("mydatabase")
    setDatabaseValidationSchema("myschema")
    ...省略其他配置...
    .build();

暂时忽略局部验证

有的时候,开发人员正在开发一个新功能,它还没有完成故无法验证。开发人员自然不会去运行还未完成的功能,期望未完成的部分不要数据库结构验证。

对此,Jimmer提供注解@org.babyfish.jimmer.sql.DatabaseValidationIgnore,该注解有以下两种用法:

  • 修饰实体接口,表示整个实体不用验证。

  • 修饰实体属性,表示特定属性不用验证。