Spring Boot数据库版本管理

gitee链接

Spring Boot版本:2.3.4.RELEASE

场景

在开发过程中,难免会因为数据库设计不合理或者没有考虑周全,而需要对表做一些修改,比如增加字段、修改字段类型等。在独立开发项目的时候顺手就改了,但是在团队协同开发的时候就不行,每个人的数据库都需要完全同步,所以需要做好数据库的版本管理。

方案一:hibernate自动更新

hibernate更新表在独立开发的时候挺有用,而且使用方便,只要修改实体类代码即可,但是缺点也明显,没有修改记录,不能删除字段。

实现方式

导入依赖:

<!--MySQL数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>
<!-- Spring-data-jpa依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
复制代码

在Spring Boot的项目中,需要在application.yml配置文件中添加:

spring:
  # 连接数据库
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://myserverhost:3306/mytest?createDatabaseIfNotExist=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: root
  # 自动更新
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
复制代码

示例代码如下(省去了不必要的内容):

@Entity
@Table(name = "tb_user")
public class User implements Serializable {
    @Id
    @Column(name = "id", columnDefinition = "BIGINT(20) UNSIGNED NOT NULL auto_increment COMMENT '主键id'")
    private Long id;

    @Column(name = "username", columnDefinition = "VARCHAR(64) UNIQUE NOT NULL COMMENT '用户名,唯一'")
    private String username;

    @Column(name = "password", columnDefinition = "VARCHAR(64) NOT NULL COMMENT '密码d'")
    private String password;
}
复制代码

这样在项目启动的时候,就会检测到实体类代码,并自动进行表的创建,修改了属性后也会自动更新。

好处

  • 够直观,不用执行sql语句,通过Java实体类就能实现变化
  • 团队内同步方便,只需要将修改后的实体类代码提交到仓库,成员同步更新即可

缺陷

  • 没有数据库的更新记录,只能通过代码仓库历史查看
  • 无法实现彻底的表字段修改,该方案会保留原有字段,添加新字段,如果更新表字段的操作多了,会导致数据库表废弃字段太多

方案二:flyway框架

flyway框架的使用稍微麻烦些,但是好处是明显的,有清晰的历史修改记录,因为是基于sql语句所以定制性很高,并且对协同开发特别友好。

实现方式

<!--h2内存数据库-->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<!--flyway数据库迁移-->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>6.4.4</version>
</dependency>
复制代码

在SpringBoot项目中,flyway的数据库管理的sql文件默认放在resources-db-migration文件夹下:

- resources
    - db
        - migration
            V1__Create_Table_User.sql
复制代码

sql文件命名格式说明:

V1__Create_Table_User.sql

V:表示文件只会被执行一次,其他的还有U和R,这里只讲V

1:版本号,可以用递增的整型或者点组合,如V1,V1.2,V2.3.4

__:分割符,必须是双下划线

Create_Table_User:描述,单下划线作为分割,这里的内容可以随意,会在更新历史中显示,如:Create Table User,这样就可以看出是创建一个新的User表。

sql文件内容

V1.0.0__Create_Table_User.sql

CREATE TABLE IF NOT EXISTS `tb_user` (
    `id` BIGINT(20) UNSIGNED AUTO_INCREMENT COMMENT '主键id',
    `username` VARCHAR(64) NOT NULL UNIQUE COMMENT '用户名,唯一',
    `password` VARCHAR(64) NOT NULL COMMENT '密码d',
    PRIMARY KEY(`id`)
) ENGINE=InnoDB COMMENT='用户表';
复制代码

V1.0.1__Add_Column_User.sql

ALTER TABLE `tb_user` ADD `email` VARCHAR(100);
复制代码

V1.0.2__Edit_Column_User.sql

ALTER TABLE `tb_user` CHANGE `email` `phone` VARCHAR(11);
复制代码

V1.0.3__Delete_Column_User.sql

ALTER TABLE `tb_user` DROP COLUMN `phone`;
复制代码

V1.0.4__Create_Table_Product.sql

CREATE TABLE IF NOT EXISTS `tb_product` (
    `id` BIGINT(20) UNSIGNED AUTO_INCREMENT COMMENT '主键id',
    `name` VARCHAR(64) NOT NULL UNIQUE COMMENT '用户名,唯一',
    `price` DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '价格',
    PRIMARY KEY(`id`)
) ENGINE=InnoDB COMMENT='产品表';
复制代码

借助flyway框架我们就可以通过原生sql语句的方式对表进行更新,和hibernate一样,在项目启动的时候就会自动执行变化。

预防失误

使用sql语句做表修改的时候,一定要进行测试,不然本来只想删除一个字段,写错成删除全部字段就麻烦了,所以在应用前,可以改成test环境,使用h2内存数据库查看最终效果,确认无误后再引用到开发/生产环境。

让我们来添加测试环境:

- resources
    application.yml
    application-dev.yml
    application-test.yml
复制代码

application.yml如下:

server:
  port: 8888
spring:
  profiles:
#    active: dev
    active: test
复制代码

application-dev.yml如下:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://myserverhost:3306/mytest?createDatabaseIfNotExist=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: admin
复制代码

application-test.yml如下:

spring:
  datasource:
    driver-class-name: org.h2.Driver
    # 内存数据库
    url: jdbc:h2:mem:DBName;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE
  h2:
    console:
      enabled: true # 开启console访问
      settings:
        trace: true # trace方便调试
        web-allow-others: true  # 允许web访问
      path: /h2 # web访问路径
复制代码

flyway需要重新创建表,所以我们把刚刚方案一里创建的tb_user表删掉,然后启动程序

启动成功后访问:http://localhost:8888/h2

直接点击connect即可跳转到表结构页面,查看表结构是否是想要的效果,因为每次启动程序都会刷新,所以通过这种办法可以不断调式,最后切换回测试或生产环境。

然后只要提交代码到Git,团队成员拉取代码后重启程序即可自动同步。

好处

  • 有清晰的数据库表更新历史记录
  • 使用原生sql语句,定制性更高

缺陷

  • 修改表的时候需要调试后再应用,没有方案一那么直观

数据库表映射到Java实体类

数据库表创建成功之后,往往需要将其转成Java实体类,手撸的话工作量大,还容易出现细节错误,借助映射工具可以很好的完成这个工作。

一开始用的是MyBatis Generator,但是没有研究出来怎么显示字段属性信息,像这样:

// 没有字段属性信息
@Id
@Column(name = "id")
private Long id;

// 有字段属性信息
@Id
@Column(name = "id", columnDefinition = "BIGINT(20) UNSIGNED NOT NULL auto_increment COMMENT '主键id'")
private Long id;
复制代码

虽然@Column注解有unique、nullable等属性,但是总不如columnDefinition显示的这么全面,所以为了满足我的需求,我花了些时间手撸了一个映射工具类DatabaseConvertUtil,支持实体类映射、Swagger注释、实体类注解,附带tk.mybatis的数据交互层自动生成,这个不必要,但是通过这个功能可以体现出自己手撸的工具能有极高的定制性。

使用说明

要测试全部功能,提前安装swagger和tkmybatis框架,当然这不是必要的,后面会有说明:

<!--swagger-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.9</version>
</dependency>

<!--Mybatis通用mapper-->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>
复制代码

DatabaseConvertUtil,主要看数据库连接和main函数即可:

// 连接数据库
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://myserverhost:3306/mytest?characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";

public static void main(String[] args) {
    doEntity = false;    // 是否添加@Id,@Column等注解
    doSwagger = true;   // 是否添加Swagger注解
    doDao = true;   // 是否生成dao类,这个不是所有人都需要,但是可以体现出高定制性

    entityPackageName = "com.cc.model.entity";  // 实体类所在的包
    daoPackageName = "com.cc.dao";  // dao所在的包

    entitySavePath = "E:\\chenc\\mygit\\mymall-demo\\flywayDemo\\src\\main\\java\\com\\cc\\model\\entity";  // 实体类包所在的绝对路径
    daoSavePath = "E:\\chenc\\mygit\\mymall-demo\\flywayDemo\\src\\main\\java\\com\\cc\\dao";   // dao包所在的绝对路径

    // 映射表明和实体类名
    alias.put("tb_user", "TbUser");
    alias.put("tb_product", "TbProduct");

    System.out.println("开始进行映射...");
    generatorEntityClass();
    System.out.println("数据库表映射到实体类完成");
}
复制代码
  • doEntity:是否需要@Entity、@Table等实体类注解

  • doSwagger:是否需要swagger注解

  • alias:映射表名和实体类名

  • savePath:实体类存放路径

  • packageName:实体类存放的包名(因为没做好自动检测,所以目前还需要手写)

  • doDao:是否需要生成dao文件(目前针对tk.mybatis)

  • daoPackageName:dao文件包名

  • daoSavePath:dao存放路径

有些默认的可以不用设置,执行main函数即可,如果出错看下是否路径没有写对。

关于属性类型

看DatabaseConvertUtil类源码importMap和typeMap,如果有缺少的可以自行添加。

flyway其他配置

spring:
  flyway:
    enabled: true # 启用
    validate-on-migrate: false  # 不校验迁移文件
    table: my_schema_history # 在微服务公用一个数据库时,为每个微服务指定一个flyway记录表名
    baseline-on-migrate: true # 指定了表名后要加上这个
    baseline-version: 0 # 指定了表名后要加上这个
复制代码

猜你喜欢

转载自juejin.im/post/7031052802411462686