目录
Flyway数据库版本控制
-
一:Flyway是什么?
Flyway是一款数据库迁移(migration)工具。简单点说,就是在你部署应用的时候,帮你执行数据库脚本的工具。Flyway支持SQL和Java两种类型的脚本,你可以将脚本打包到应用程序中,在应用程序启动时,由Flyway来管理这些脚本的执行,这些脚本被Flyway称之为migration。
就目前而言,我们部署应用的流程大概是这样的:
开发人员将应用程序打包、按顺序汇总并整理数据库升级脚本
DBA拿到数据库升级脚本检查、备份、执行,以完成数据库升级
应部署人员拿到应用部署包,备份、替换,以完成应用程序升级
引入Flyway之后的应用部署流程大概是这样的:
开发人员将应用程序打包
应部署人员拿到应用部署包,备份、替换,以完成应用程序升(Flyway将自动执行升级/备份脚本)
-
二:为什么需要做数据库版本控制?
我们在公司做开发时,由于项目需求的变化,或者前期设计缺陷,导致在后期需要修改数据库,这应该是一个比较常见的事情,如果项目还没上线,你可能把表删除了重新创建,但是如果项目已经上线了,就不能这样简单粗暴了,我们需要通过 SQL 脚本在已有数据表的基础上进行升级。
在日常开发中,我们经常会遇到下面的问题:
自己写的SQL忘了在所有环境执行;
别人写的SQL我们不能确定是否都在所有环境执行过了;
有人修改了已经执行过的SQL,期望再次执行;
需要新增环境做数据迁移;
每次发版需要手动控制先发DB版本,再发布应用版本;
其它场景...
在多人开发的项目中,我们都习惯了使用SVN或者Git来对代码做版本控制,主要的目的就是为了解决多人开发代码冲突和版本回退的问题。其实,数据库的变更也需要版本控制,
目前 Java 这块,想要对数据库的版本进行管理主要有两个工具:
两个工具各有千秋,但是核心功能都是数据库的版本管理,这里主要来看 Flyway。就像我们使用 Git 来管理代码版本一样,Flyway 可以用来管理数据库版本。
-
三: 数据库版本控制可以通过哪些手段?
备份数据库
解决这个麻烦我们最开始的思路就是备份数据库,每隔几天备份一次数据库,当需要找历史数据库对象时将备份库还原到测试机上再把对应的存储过程或者表定义找出来。这种方式使用过程中就发现很多问题,首先每次需要查看历史记录的时候去还原备份非常麻烦,其次随着开发的进行,很难记得清是谁在什么时间改了什么数据库对象,必须把一段时间内的备份都还原了再进行查找。这个过程不仅让人很恼火,也非常浪费时间精力,如果备份间隔过长,期间变更的对象定义就无法找回了。
导出数据库对象
后来的解决思路就是每次修改数据库对象的时候将数据库对象的定义以.sql的方式存入SVN,这种方式非常麻烦。很多时候开发人员偷懒忘记签入SVN了,则会引起很大问题。新加入团队的人也很难看清版本修改的来龙去脉。此外,这种方式非常不直观,如果希望看到一个数据库对象的历史版本记录,则还要去SVN中查找,显示也非常不直, 还有一个问题是团队中的开发DBA非常不习惯使用SVN。
将版本记录存入数据库
这种方式是我在网上看到过的,是在数据库中通过触发器对于数据库的变更操作进行记录,作为开发出身的人来说,对这种方式就不是很喜欢。况且去历史表中找版本修改记录本来就是一件费时费力的事。
使用第三方工具
现在一些第三方公司已经开发了针对数据库的版本控制工具.
-
四:Flyway的使用及注意事项
准备数据库
首先,我们需要准备好一个空的数据库。(数据库的安装和账密配置此处忽略)
此处以mysql为例,在本地电脑上新建一个空的数据库,名称叫做flyway,我们通过dbeaver看到的样子如下:
新建一个空的数据库
1:SpringBoot工程使用flyway
准备SpringBoot工程
在start.spring.io上新建一个SpringBoot工程,要求能连上自己本地新建的mysql数据库flyway,这个步骤也比较简单,就不再细讲。
但要注意的是,application.properties中数据库的配置务必配置正确,下述步骤中系统启动时,flyway需要凭借这些配置连接到数据库。这里贴一份:
# db config
spring.datasource.url=jdbc:mysql://localhost:3306/flyway?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
flyway的引入与尝试
首先,在pom文件中引入flyway的核心依赖包:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>5.2.4</version>
</dependency>
其次,在src/main/resources目录下面新建db.migration文件夹,默认情况下,该目录下的.sql文件就算是需要被flyway做版本控制的数据库SQL语句。
但是此处的SQL语句命名需要遵从一定的规范,否则运行的时候flyway会报错。命名规则主要有两种:
-
仅需要被执行一次的SQL命名以大写的"V"开头,后面跟上"0~9"数字的组合,数字之间可以用“.”或者下划线"_"分割开,然后再以两个下划线分割,其后跟文件名称,最后以.sql结尾。比如,
V2.1.5__create_user_ddl.sql
、V4.1_2__add_user_dml.sql
。 -
可重复运行的SQL,则以大写的“R”开头,后面再以两个下划线分割,其后跟文件名称,最后以.sql结尾。。比如,
R__truncate_user_dml.sql
。
其中,V开头的SQL执行优先级要比R开头的SQL优先级高。
如下,我们准备了三个脚本,分别为:
-
V1__create_user.sql
,其中代码如下,目的是建立一张user表,且只执行一次。CREATE TABLE IF NOT EXISTS `USER`( `USER_ID` INT(11) NOT NULL AUTO_INCREMENT, `USER_NAME` VARCHAR(100) NOT NULL COMMENT '用户姓名', `AGE` INT(3) NOT NULL COMMENT '年龄', `CREATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `CREATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN', `UPDATED_TIME` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `UPDATED_BY` varchar(100) NOT NULL DEFAULT 'UNKNOWN', PRIMARY KEY (`USER_ID`) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
V2__add_user.sql
,其中代码如下,目的是往user表中插入一条数据,且只执行一次。insert into `user`(user_name,age) values('lisi',33);
-
R__add_unknown_user.sql
,其中代码如下,目的是每次启动倘若有变化,则往user表中插入一条数据。insert into `user`(user_name,age) values('unknown',33);
与之相对应的目录截图如下:
项目目录结构
其中2.1.6、2.1.7和every的文件夹不会影响flyway对SQL的识别和运行,可以自行取名和分类。
「注意」
这个如果创建项目时就选择了 Flyway 依赖,就会有这个目录
到这一步,flyway的默认配置已经足够我们开始运行了。此时,我们启动SpringBoot的主程序,如果以上步骤没有配置错误的话,运行截图如下:
flyway成功运行
此时,我们刷新数据库,可以看到flyway的历史记录表已经生成并插入了三个版本的记录:
flyway_schema_history
而且,user表也已经创建好了并插入了两条数据:
user
我们不改变任何东西,再次执行主程序,日志如下:
再次执行flyway
两张数据库表中的内容也毫无任何变化。
可是,如果我们修改V2__add_user.sql
中的内容,再次执行的话,就会报错,提示信息如下:
[ERROR] Migration checksum mismatch for migration version 2
如果我们修改了R__add_unknown_user.sql
,再次执行的话,该脚本就会再次得到执行,并且flyway的历史记录表中也会增加本次执行的记录。
2:作为jar包在普通工程中使用
1、将flyway-core-2.3.jar放到项目lib中,下载地址:http://flywaydb.org/getstarted/download.html
2、在src目录下建立保存sql版本文件的路径:src/db/migration,flyway默认查找路径,可以改,但没必要。
3、在sql版本文件路径中增加sql文件,命名规则,如:V1__2014_4_13.sql ,V开头+版本号+双下划线+描述,描述中可以有下划线,后缀为sql。
4、增加flyway的java类,有命令行工具,但还是java类用起来方便,如下:
package com.cms.flyway;
import java.io.IOException;
import java.util.Properties;
import com.googlecode.flyway.core.Flyway;
public class FlywayApp {
// 读取数据库配置参数
private static Properties config = new Properties();
static {
try {
config.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("activerecord.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
// 执行数据库版本升级
public static void migration() {
// Create the Flyway instance
Flyway flyway = new Flyway();
// Point it to the database
flyway.setDataSource(config.getProperty("com.et.ar.ActiveRecordBase.url"), config.getProperty("com.et.ar.ActiveRecordBase.username"), config.getProperty("com.et.ar.ActiveRecordBase.password"));
flyway.setInitOnMigrate(true);
// Start the migration
flyway.migrate();
}
}
<!-- 数据库自动更新 -->
<bean id="baselineVersion" class="org.flywaydb.core.api.MigrationVersion">
<constructor-arg value="2.5"/>
</bean>
<bean id="flyway" class="org.flywaydb.core.Flyway" init-method="migrate">
<property name="dataSource" ref="dataSource"/>
<property name="baselineOnMigrate" value="true" />
<property name="baselineVersion" ref="baselineVersion" />
<property name="outOfOrder" value="true" />
</bean>
<!--整合hibernate-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" depends-on="flyway" >
<!--方案一:Druid数据源配置-->
<property name="dataSource" ref="dataSource"></property>
<!--方案二:数据源配置-->
<!--<property name="dataSource" ref="myP6DataSource"></property>-->
<property name="hibernateProperties">
<value>
hibernate.dialect=${jdbc.hibernate.dialect}
hibernate.show_sql=${jdbc.hibernate.show_sql}
hibernate.format_sql=${jdbc.hibernate.format_sql}
hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
hibernate.id.new_generator_mappings=true
<!-- 配置hibernate的二级缓存 -->
hibernate.cache.use_query_cache=true
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
</value>
</property>
<property name="packagesToScan">
<value>com.founder.mrp.domain</value>
</property>
</bean>
从上面的bean 定义中我们可以看到,我们为flywayMigration 这个bean 实例注入了一个数据源,Flyway 的所有操作将针对这个数据源进行;同时我们通过init-method 属性指定了Spring 在实例化该bean 以后,主动执行该bean 的migrate 方法,而该方法内会执行Flyway 更新数据库的操作。至此,我们达到了在应用启动时,Spring 实例化上下文的时候,在Spring 实例化flywayMigration 这个bean 的时候,自动执行Flyway 更新数据库的操作。
但是,我们还没有达到目的,万一Flyway 还在更新数据库,没有完成更新操作之前,应用程序的其他逻辑已经开始使用数据库进行其他操作了,会导致应用程序产生很多bug ,甚至根本运行不起来。
要解决这个问题,我们可以利用Spring 的bean 依赖原理,让关键的数据库操作bean 依赖于flywayMigration 这个bean ,达到在flywayMigration 没有实例化完成(数据库更新操作完成)之前,不能进行任何其他数据库相关操作。
利用Spring 的bean 依赖让flywayMigration 优先处理数据库更新操作:
<!-- 将连接池注入到 JdbcTemplate对象 -->
<beanid="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate"depends-on="flywayMigration1">
<property name="dataSource"ref="dataSource"></property>
</bean>
至此,运行就可以了。
5、在服务器启动的时候或者定时器 执行该类的migration()方法即可。
6、第一次执行会生成一个专门存放数据库schema_version的表
7、以后数据库有了新的改动,导出新版本sql文件(如:mysqldump -u -p databasename>/xx.sql)改为新版本命名文件放到db.migration路径下,flyway会自动帮你更新数据库版本的。
3: maven插件的使用
以上步骤中,每次想要migration都需要运行整个springboot项目,并且只能执行migrate一种命令,其实flyway还是有很多其它命令的。maven插件给了我们不需要启动项目就能执行flyway各种命令的机会。
在pom中引入flyway的插件,同时配置好对应的数据库连接。
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>5.2.4</version>
<configuration>
<url>jdbc:mysql://localhost:3306/flyway?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT</url>
<user>root</user>
<password>root</password>
<driver>com.mysql.cj.jdbc.Driver</driver>
</configuration>
</plugin>
然后更新maven插件列表,就可以看到flyway的全部命令了。
flyway的各种命令
此时,我们双击执行上图中的flyway:migrate的效果和启动整个工程执行migrate的效果是一样的。
其它命令的作用如下列出,各位可自行实验体会:
-
baseline
对已经存在数据库Schema结构的数据库一种解决方案。实现在非空数据库新建MetaData表,并把Migrations应用到该数据库;也可以在已有表结构的数据库中实现添加Metadata表。
-
clean
清除掉对应数据库Schema中所有的对象,包括表结构,视图,存储过程等,clean操作在dev 和 test阶段很好用,但在生产环境务必禁用。
-
info
用于打印所有的Migrations的详细和状态信息,也是通过MetaData和Migrations完成的,可以快速定位当前的数据库版本。
-
repair
repair操作能够修复metaData表,该操作在metadata出现错误时很有用。
-
undo
撤销操作,社区版不支持。
参考:https://blog.csdn.net/chenleiking/article/details/80691750
https://mp.weixin.qq.com/s/yabpyA90D1yUtWRNr330yA
https://www.jianshu.com/p/567a8a161641
https://www.cnblogs.com/loong-hon/p/12874633.html