# 数据库管理

项目版本迭代中遇到的数据库表、列、外键索引变更等场景时,需要使用到数据库版本管理工具进行管理。

FlyORM模块也提供了相关的功能,在配置的数据源中创建一个元数据表来保存迁移记录,通过每次工程启动时读取该表信息来达到独立于应用实现管理并跟踪数据库变更的目的,支持编写SQL脚本或Java实现类。

此外,Fly默认开启实体模型自动建表、补全字段和外键约束,有助于在初始开发阶段减少对数据库频繁手工修改模型。

# 使用基础

需要引入fly-orm模块:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-orm</artifactId>
    <version>${fly.version}</version>
</dependency>

Spring启动类或配置类上添加@EnableMigration注解:

import fly.web.EnableMigration;

@SpringBootApplication
@EnableMigration
public class DemoApplication {...}

# 初步了解

# 数据结构

使用数据迁移,先准备必要的数据源,示例使用h2数据库:

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: "jdbc:h2:file:./target/dev;AUTO_SERVER=true;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
    username: sa
    password: 1

启动项目时,会在数据库读取或初始数据迁移记录表,该表名为fly_migration,表结构及简单数据示例如下:

暂只展示便于理解使用规范的列,部分用于内部处理的列未列出,想深入了解可查看fly.orm.migrate.model.MigrationEntity

ns_ name_ type_ version_ source_ desc_ data_ status_
system 1 VERSIONED 1 null initial migration tables null EXECUTED
default.app_init pre_app PRE null app.migrate.appinit.Pre__app null {} EXECUTED
default 1.0.0 VERSIONED 1.0.0 V1.0.0__init.sql init {"statements":[...]} EXECUTED

其中ns_name_列为复合主键,各列简单说明:

  • ns_:命名空间namespace的缩写,一般存储命名空间、命名空间+模块或特殊值
  • name_:一般为版本号或类名,由于SQL脚本没有像迁移实现类那样可以自定义name,结合复合主键的因素,因此一个命名空间内不能出现重复版本的SQL执行脚本
  • type_:迁移类型,版本化为VERSIONEDfly.orm.migrate.model.MigrationType
  • version_:版本号,可以在该列查看已执行的最高版本,后续工程启动只执行高于已执行最高版本的版本化迁移脚本或类
  • source_:该次迁移的来源,为文件名或类路径
  • desc_:描述,来自迁移文件名中的描述部分,具体可看下文
  • data_:迁移执行的内容,比如已执行的语句statements,缓存的模型等(fly.orm.migrate.model.MigrationData
  • status_:执行状态,EXECUTED为已执行,EXECUTING待执行/执行中

# 执行顺序

可结合fly.orm.migrate.model.MigrationType了解:

  1. JavaPRE_ONCE(版本前只执行一次)
  2. JavaPRE_REPEATABLE(版本前重复执行)
  3. SQLPRE_ONCE(版本前只执行一次)
  4. SQLPRE_REPEATABLE(版本前重复执行,ddlAuto所在级别)
  5. 版本执行,从低版本到高版本,其中又有每个版本的版本前/后执行;
  6. SQLPOST_REPEATABLE(版本后重复执行,ddlAuto所在级别)
  7. SQLPOST_ONCE(版本后只执行一次)
  8. JavaPOST_REPEATABLE(版本后重复执行)
  9. JavaPOST_ONCE(版本后只执行一次)

# 命名空间

数据库管理中的命名空间,用于隔离如不同模块的数据迁移进度,每个命名空间管理各自的已执行脚本和最新迁移版本,在一个Spring应用上下文中,默认只执行default命名空间,其他非默认命名空间需要配置指定开启

fly:
  migration:
    namespaces: 
      - default
      - namespace1
      - namespace2

若想要在不同应用上下文中启用不同的命名空间脚本,可以在@EnableMigration注解中指定:

import fly.web.EnableMigration;

@SpringBootApplication
@EnableMigration(namespaces = "namespace1")
public class DemoApplication {...}

更多配置可进注解内查看,命名空间脚本定义见:SQL 脚本迁移实现类

# 迁移配置

配置前缀:fly.migration

具体配置类:fly.orm.migrate.MigrationProperties

配置 说明 类型 默认值
enabled 是否开启数据迁移,在已添加@EnableMigration注解的基础上支持配置式开关 boolean true
namespaces 默认启用的命名空间数组,在初步了解中可知命名空间起到关键的迁移历史数据隔离作用,假设有多个业务模块对同一数据库进行版本管理的情况,就可以通过命名空间达到各自独立管理的效果 String[] ["default"]
defaultLocations SQL数据迁移脚本文件默认命名空间存储路径列表 String[] ["classpath*:META-INF/migration", "classpath:migration"]
namespaceLocations SQL数据迁移脚本文件非默认命名空间存储路径列表 String[] ["classpath*:migrations"]
ddlAuto 是否开实体模型自动执行数据定义语言,包括建表、索引、外键约束等 boolean true
ddlCaching 是否读取用于ddl的已扫描的数据库模型缓存,具体缓存位置见下文 boolean true
mode 迁移模式,默认auto为工程启动时自动执行 String auto

application.yml 简单示例:

fly:
  migration:
    ddl-caching: false
    default-locations:
      - 'classpath*:migrations'
      - 'classpath*:app-init'
  • ddl缓存位置

fly_migration表中,在当前命名空间及筛选_namepre_ddl_auto可得示例:

ns_ name_ type_ version_ data_
default pre_ddl_auto PRE null {"content":[...],"statements":[...]}

data数据的content为已扫描的模型对象列表,statements为框架已执行的ddl语句。特殊使用场景中,若开启了缓存,却手动修改了数据库且没有更新缓存,导致工程运行中拿到非最新的模型缓存数据,从而执行ddl异常,可以考虑删除该缓存数据,重新启动会自动再次扫描生成。

# 定义 SQL 脚本

迁移配置可知,以默认命名空间举例,我们可以在classpath*:migration路径中定义SQL迁移脚本:

在创建SQL脚本文件,文件名有一定的命名规则,关系到该脚本的功能类型及所属版本、迁移描述等。根据功能类型,划分了以下几点讲解:

# 版本化

命名规则V{version}_{desc}

其中迁移描述 {desc}可选,无描述时为V{version}。示例:V1.0.0

支持的文件类型.sql / .yml

数据迁移的迭代更新,就是基于版本实现的,版本化的脚本文件相当于入口,此类型脚本只会执行一次,执行成功后在fly_migration产生记录,只要版本小于或等于已执行成功的最高版本,将不再执行。

  • .sql文件类型

内容可为普通的SQL或带特殊语法的SQL(详细见SQL模板),可带注释,其中--注释会进行解析用于拓展功能语句块引用

  • .yml文件类型

与基础的SQL不同的是,通过yml格式数据简化语句的编写,也便于框架对数据进行解析,做一些额外的处理,比如不用加额外的标记语法,直接写实体名称及字段名,最后通过框架生成的SQL会自动转换,又如直接写多组有序数据,自动生成多个INSERT语句。

# 版本前/后执行

命名规则

  1. 版本前只执行一次:PRE__O_{name},对应迁移类型:PRE_ONCE
  2. 版本前重复执行:PRE__R_{name},对应迁移类型:PRE_REPEATABLE
  3. 版本后只执行一次:POST__O_{name},对应迁移类型:POST_ONCE
  4. 版本后重复执行:POST__R_{name},对应迁移类型:POST_REPEATABLE

# 脚本引用

暂只支持.sql类型的文件使用该功能。

脚本引用有助于提高脚本利用率,还支持区分数据源类型。引用入口是在SQL脚本的单行注释中使用@include指定引用方式,引用方式又分为两种,视情况选择:

  1. 单纯指定引用名称,引用名称需要有对应的脚本文件

引用名称对应的脚本文件有规定的命名规则:V{version}@{includeName}.{dbType}

可以理解为@符号视为可被引用,后面的引用名称{includeName}自定义且必填,归属版本V{version}、所属数据库类型{dbType}可选,枚举参考:

  • @{includeName}.sql - 无额外限制
  • @{includeName}.mysql.sql - 只在数据源类型为mysql中有效
  • V1.0.0@{includeName}.sql - 只在1.0.0版本中有效
  • V1.0.0@{includeName}.mysql.sql - 只在1.0.0版本且数据源类型为mysql中有效

该类型文件存粹用于被引用,在未被引用的情况下,不会作为正常的数据迁移文件被执行,且当引用后不符合条件也不会最终执行。示例如下:

版本执行文件V1.0.0.sql,引用initDialect

INSERT INTO entity (id, name) values('1', '1.0.0');

-- @include initDialect

引用名称initDialect对应有两个文件:

V1.0.0@initDialect.h2.sql

INSERT INTO entity (id, name) values('dialect', 'h2');

V1.0.0@initDialect.mysql.sql

INSERT INTO entity (id, name) values('dialect', 'mysql');

若当前使用的数据库为h2,则最终执行的SQL为:

INSERT INTO entity (id, name) values('1', '1.0.0');

INSERT INTO entity (id, name) values('dialect', 'h2');

若当前使用的数据库为oracle,则最终执行的SQL为:

INSERT INTO entity (id, name) values('1', '1.0.0');

{dbType}常见的有:h2mysqloracledm(达梦)、kingbase(人大金仓)等,如果无法确定数据库类型,可在控制台INFO级别日志查找Current dialect name:查看。

  1. classpath指定文件路径

该方式也支持兼容不同类型数据库,设存在:

classpath:fly/migration/initDialect.sql

classpath:fly/migration/initDialect.mysql.sql

引用方式:

-- @include classpath:fly/migration/initDialect

最终查找引用文件时,优先获取符合当前数据源类型的SQL文件,即如果是mysql,取classpath:fly/migration/initDialect.mysql.sql,否则取classpath:fly/migration/initDialect.sql。不存在不报错。

# 命名空间

依据fly.migration.namespace-locations配置,我们可以在classpath*:migrations下定义非默认命名空间的脚本,每个命名空间维护独立的脚本和版本,目录文件结构如下({namespace}需要自定义命名):

该路径下的第一层文件夹作为命名空间名称。

# 定义迁移实现类

实现fly.orm.migrate.MigrationExecutor接口,并注册为bean。使用场景及示例如下。

# 每个版本前/后执行

命名规则Pre__V{version} / Post__V{version}

其中版本号用_分隔,如Post__V1_0_0。指定版本数据迁移必须存在,否则将不执行。

import fly.orm.migrate.MigrationContext;
import fly.orm.migrate.MigrationExecutor;
import org.springframework.stereotype.Component;

@Component
public class Post__V1_0_0 implements MigrationExecutor {

    @Override
    public void migrate(MigrationContext context) {
        // 在1.0.0版本数据迁移后执行
    }

}

# 版本前/后执行

命名规则

  1. 版本前只执行一次:PRE__O_{name},对应迁移类型:PRE_ONCE
  2. 版本前重复执行:PRE__R_{name},对应迁移类型:PRE_REPEATABLE
  3. 版本后只执行一次:POST__O_{name},对应迁移类型:POST_ONCE
  4. 版本后重复执行:POST__R_{name},对应迁移类型:POST_REPEATABLE

# 命名空间

使用@MigrationNamespace注解指定命名空间,未指定则归属默认命名空间:

import fly.orm.migrate.MigrationContext;
import fly.orm.migrate.MigrationExecutor;
import org.springframework.stereotype.Component;
import fly.orm.migrate.annotation.MigrationNamespace;

@Component
@MigrationNamespace("namespace1")
public class Pre__V1_0_0 implements MigrationExecutor {

    @Override
    public void migrate(MigrationContext context) {
        // ...
    }
}
顶部