之前已经介绍了很多在Spring Boot中使用MySQL的案例,包含了Spring Boot最原始的 JdbcTemplate(https://blog.didispace.com/spring-boot-learning-21-3-1/)、Spring Data JPA(https://blog.didispace.com/spring-boot-learning-21-3-4/)以及我们国内最常用的MyBatis。同时,对于一些复杂场景比如:更换Druid数据源,或是多数据源的情况也都做了介绍。

不论我们使用哪一个具体实现框架,都离不开对数据库表结构的管理。而这一类管理一直都存在一个问题:由于数据库表元数据存储于数据库中,而我们的访问逻辑都存在于Git或其他代码仓库中。Git已经帮助我们完成了代码的多版本管理,那么数据库中的表该如何做好版本控制呢?

今天我们就来介绍在Spring Boot中使用Flyway来管理数据库版本的方法。

Flyway简介

Flyway是一个简单开源数据库版本控制器(约定大于配置),主要提供migrate、clean、info、validate、baseline、repair等命令。它支持SQL(PL/SQL、T-SQL)方式和Java方式,支持命令行客户端等,还提供一系列的插件支持(Maven、Gradle、SBT、ANT等)。

官方网站:https://flywaydb.org/

本文对于Flyway的自身功能不做过多的介绍,读者可以通过阅读官方文档或利用搜索引擎获得更多资料。下面我们具体说说在Spring Boot应用中的应用,如何使用Flyway来创建数据库以及结构不一致的检查。

动手试试

下面我们先预设一个开发目标:

  1. 假设我们需要开发一个用户管理系统,那么我们势必要设计一张用户表,并实现对用户表的增删改查操作。

  2. 在任务1的功能完成之后,我们又有一个新需求,需要对用户表增加了一个字段,看看如何实现对数据库表结构的更改。

目标 1 的实现

第一步:创建一个基础的Spring Boot项目,并在pom.xml中加入Flyway、MySQL连接和数据访问相关的必要依赖(这里选用spring-boot-starter-jdbc作为例子)

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.flywaydb</groupId><artifactId>flyway-core</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

第二步:按Flyway的规范创建版本化的SQL脚本。

  • 在工程的src/main/resources目录下创建db目录,在db目录下再创建migration目录

  • 在migration目录下创建版本化的SQL脚本V1__Base_version.sql


DROP TABLE IF EXISTS user ;
CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(20) NOT NULL COMMENT '姓名',`age` int(5) DEFAULT NULL COMMENT '年龄',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意:如果你不想将SQL脚本放到其他目录,可以用spring.flyway.locations参数来配置。这里不同于1.x版本的配置项flyway.locations

第三步:根据User表的结构,编写对应的实体定义


@Data
@NoArgsConstructor
public class User {private Long id;private String name;private Integer age;}

第四步:编写用户操作接口和实现


public interface UserService {/*** 新增一个用户** @param name* @param age*/int create(String name, Integer age);/*** 根据name查询用户** @param name* @return*/List<User> getByName(String name);/*** 根据name删除用户** @param name*/int deleteByName(String name);/*** 获取用户总量*/int getAllUsers();/*** 删除所有用户*/int deleteAllUsers();}@Service
public class UserServiceImpl implements UserService {private JdbcTemplate jdbcTemplate;UserServiceImpl(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Overridepublic int create(String name, Integer age) {return jdbcTemplate.update("insert into USER(NAME, AGE) values(?, ?)", name, age);}@Overridepublic List<User> getByName(String name) {List<User> users = jdbcTemplate.query("select * from USER where NAME = ?", (resultSet, i) -> {User user = new User();user.setId(resultSet.getLong("ID"));user.setName(resultSet.getString("NAME"));user.setAge(resultSet.getInt("AGE"));return user;}, name);return users;}@Overridepublic int deleteByName(String name) {return jdbcTemplate.update("delete from USER where NAME = ?", name);}@Overridepublic int getAllUsers() {return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);}@Overridepublic int deleteAllUsers() {return jdbcTemplate.update("delete from USER");}}

这里主要介绍Flyway的应用,所以采用这种比较简单的编写方式,实际项目应用中,还是推荐MyBatis的具体操作实现。

第五步:编写测试用例


@Slf4j
@SpringBootTest
public class Chapter311ApplicationTests {@Autowiredprivate UserService userSerivce;@Testpublic void test() throws Exception {userSerivce.deleteAllUsers();// 插入5个用户userSerivce.create("Tom", 10);userSerivce.create("Mike", 11);userSerivce.create("Didispace", 30);userSerivce.create("Oscar", 21);userSerivce.create("Linda", 17);// 查询名为Oscar的用户,判断年龄是否匹配List<User> userList = userSerivce.getByName("Oscar");Assertions.assertEquals(21, userList.get(0).getAge().intValue());// 查数据库,应该有5个用户Assertions.assertEquals(5, userSerivce.getAllUsers());// 删除两个用户userSerivce.deleteByName("Tom");userSerivce.deleteByName("Mike");// 查数据库,应该有5个用户Assertions.assertEquals(3, userSerivce.getAllUsers());}}

注意由于Spring Boot 2.4应用的junit版本与之前Spring Boot 1.x版本中的不同,因此单元测试的编写略有区别,有兴趣的读者可以分别查看之前介绍文章和这篇文章中的单元测试的区别,这里就不细说了。

第六步:运行上面编写的单元测试,验证一下效果。

不出意外,单元测试运行ok的话

连上数据库看看。此时应该多出了这两张表:

  • user表就是我们维护在SQL脚本中要创建的表

  • flyway_schema_history表是flyway的管理表,用来记录在这个数据库上跑过的脚本,以及每个脚本的检查依据。这样每次应用启动的时候,就可以知道哪个脚本需要运行,或者哪个脚本发生了变动,运行基础可能不对,造成数据结构的混乱而阻止运行。

目标 2 的实现

有了上面的基础之后,我们来说说后续要做表结构的表变动该怎么操作,这也是之前读者出现问题最多的情况,所以在2.x版本教程中特地讲一讲。

首先,大家在开始使用Flyway之后,对于数据库表接口的变更就要关闭这几个途径:

  1. 直接通过工具登录数据去修改表结构

  2. 已经发布的sql脚本不允许修改

正确的表结构调整途径:在flyway脚本配置路径下编写新的脚本,启动程序来执行变更。这样可以获得几个很大的好处:

  1. 脚本受Git版本管理控制,可以方便的找到过去的历史

  2. 脚本在程序启动的时候先加载,再提供接口服务,一起完成部署步骤

  3. 所有表结构的历史变迁,在管理目录中根据版本号就能很好的追溯

下面根据一个实际需求来具体操作下。假设我们现在想对User表增加一个字段:address,用来存储用户的通讯地址,那么我们就需要这样操作实现。

第一步:创建脚本文件V1_1__alter_table_user.sql,并写入增加address列的语句


ALTER TABLE `user` ADD COLUMN `address` VARCHAR(20) DEFAULT NULL;

对于脚本文件名的基本规则是:版本号__描述.sql。当然如果你有更细致的要求,那么可以做更细致的文件名规划,具体细节读者可以查阅文末参考资料中的官方文档获取。

第二步:再次执行单元测试,在控制台中可以看到如下日志:


2021-01-11 16:58:12.025  INFO 37330 --- [           main] o.f.c.i.database.base.DatabaseType       : Database: jdbc:mysql://localhost:3306/test (MySQL 8.0)
2021-01-11 16:58:12.063  INFO 37330 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 2 migrations (execution time 00:00.020s)
2021-01-11 16:58:12.075  INFO 37330 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema `test`: 1
2021-01-11 16:58:12.082  INFO 37330 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `test` to version "1.1 - alter table user"
2021-01-11 16:58:12.113  INFO 37330 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema `test` (execution time 00:00.045s)

再查看一下数据中国的内容:

如果你还没有体会到引入Flyway对给我们的表结构带来的好处的话,不妨也留言分享下你们的管理方式吧!

更多本系列免费教程连载「点击进入汇总目录」(https://blog.didispace.com/spring-boot-learning-2x/)

代码示例

本文的相关例子可以查看下面仓库中的chapter3-11目录:

  • Github:https://github.com/dyc87112/SpringBoot-Learning/

  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!

参考资料

  • Spring Boot中使用Flyway来管理数据库版本(https://blog.didispace.com/spring-boot-flyway-db-version/)

  • Flyway官方文档

往期推荐

因脉脉上的匿名消息,拼多多员工被开除了!

你一定需要知道的高阶JAVA枚举特性!

盘点 IDEA2020.3 那些炫酷的插件,超级好用的那种

不到 20 人的 IT 公司该去吗?

文件上传的单元测试怎么写?

Github上看到的4个好玩的开源项目

Spring Boot 2.x基础教程:使用Flyway管理数据库版本相关推荐

  1. Spring Boot 2.x基础教程:事务管理入门

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 什么是事务? 我们在开发企业应用时,通常业务人员的一个操 ...

  2. Spring Boot 2.x基础教程:使用集中式缓存Redis

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 之前我们介绍了两种进程内缓存的用法,包括Spring B ...

  3. Spring Boot 2.x基础教程:使用Elastic Job的分片配置

    上一篇,我们介绍了如何使用Elastic Job实现定时任务(https://blog.didispace.com/spring-boot-learning-2-7-2/).解决了使用@Schedul ...

  4. Spring Boot 2.x基础教程:使用Elastic Job实现定时任务

    上一篇,我们介绍了如何使用Spring Boot自带的@Scheduled注解实现定时任务(https://blog.didispace.com/spring-boot-learning-2-7-1/ ...

  5. Spring Boot 2.x基础教程:使用Redis的发布订阅功能

    通过前面一篇集中式缓存的使用教程,我们已经了解了Redis的核心功能:作为K.V存储的高性能缓存. 接下来我们会分几篇来继续讲讲Redis的一些其他强大用法!如果你对此感兴趣,一定要关注收藏我哦! 发 ...

  6. Spring Boot 2.x基础教程:如何扩展XML格式的请求和响应

    在之前的所有Spring Boot教程中,我们都只提到和用到了针对HTML和JSON格式的请求与响应处理.那么对于XML格式的请求要如何快速的在Controller中包装成对象,以及如何以XML的格式 ...

  7. Spring Boot 2.x基础教程:使用JTA实现分布式事务

    在一个Spring Boot项目中,连接多个数据源还是比较常见的.之前也介绍了如何在几种常用框架的场景下配置多数据源,具体可见: Spring Boot 2.x基础教程:JdbcTemplate的多数 ...

  8. Spring Boot 2.x基础教程:多文件的上传

    昨天,我们介绍了如何在Spring Boot中实现文件的上传(博客地址:https://blog.didispace.com/spring-boot-learning-21-4-3/).有读者问:那么 ...

  9. Spring Boot 2.x基础教程:使用EhCache缓存集群

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 上一篇我们介绍了在Spring Boot中整合EhCac ...

  10. Spring Boot 2.x基础教程:MyBatis的多数据源配置

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 前两天,我们已经介绍了关于JdbcTemplate的多数 ...

最新文章

  1. windows系统服务器数据库,windows系统的服务器肿么访问数据库
  2. 支架预压弹性变形值计算_桥梁支架的预压方法与流程
  3. [译]以PostgreSQL为例,谈join计算的代价
  4. emwin自定义消息问题
  5. 外部工具连接SaaS模式云数据仓库MaxCompute实战:商业BI分析工具篇
  6. css 计算属性的应用_如何使用一点CSS Grid魔术设计计算器应用
  7. JasperReport报表设计总结
  8. win10系统哪个是索引服务器,win10系统下控制面板没有索引如何解决
  9. 【BUAA_CO_LAB】p5p6碎碎念
  10. KEIL5下载并安装STM32包
  11. java的小于等于符号怎么打_「小于符号」mybatis的一些特殊符号标识(大于,小于,等于,不等于) - seo实验室...
  12. django.relatedField-关联域
  13. ubuntu下如何打开root文件夹,如何用root权限管理员权限打开文件夹,以及如何获取root权限
  14. 百度网盘Linux版本能用吗,百度网盘Linux版使用体验效果
  15. 什么是SD-WAN?网络技术科普
  16. 新职业人工智能工程师要学哪些内容?
  17. KDE-Graphics(KDE图形图像软件)先容
  18. Zipf's law分析
  19. alueError: With n_samples=1, test_size=0.2 and train_size=None, the resulting train set will be
  20. 牛客每日练习----珂朵莉与宇宙,Forever97与寄信,Number

热门文章

  1. 输入的命令集锦(补充)
  2. Exchange Server 2007客户端访问协议部署SSL
  3. 用Command实现校验器
  4. linux centos rc.local 自启动无效 解决方法
  5. python3 多进程库 multiprocessing 使用简介
  6. %00截断攻击的探索
  7. php中$_REQUEST、$_POST、$_GET的区别
  8. STL中的list详解
  9. 反弹端口 HTTP代理 HTTP隧道技术
  10. OD命令大全(OllyDbg命令)