官方文档

https://flywaydb.org/getstarted/firststeps/api[https://flywaydb.org/getstarted/firststeps/api]

入门示例

Java代码

  1. package foobar;
  2. import org.flywaydb.core.Flyway;
  3. public class App {

  4. public static void main(String[] args) {

  5. Flyway flyway = new Flyway();

  6. // 指定数据源

  7. flyway.setDataSource("jdbc:mysql://localhost/test", "root", "root");

  8. // 开始数据迁移

  9. flyway.migrate();

  10. }

  11. }

在classpath下添加SQL文件 db/migration/V1__Create_person_table.sql

  1. create table PERSON (
  2. ID int not null,
  3. NAME varchar(100) not null
  4. );

运行程序,在test数据库会自动创建PERSON表。

后续新增表和字段,只需在db/migration目录下新增SQL文件,格式为V${version}__${name}.sql,version值依次增加,比如V2__name.sql,V3__name.sql。

原理介绍

Flyway的数据库迁移的实现原理是,从classpath或文件系统中找到符合规则的数据库迁移脚本,比如db/migration目录下命名规则为V${version}__${name}.sql的文件,将脚本按照version进行排序,依次执行。执行过的脚本会作为一条记录,存储在schema_version表中。当下次执行迁移时,判断脚本已经执行,则跳过。

MigrationResolver接口负责查找数据库迁移脚本,方法为resolveMigrations(),数据库迁移脚本用ResolvedMigration对象表示。MigrationResolver包含多种实现类,比如SqlMigrationResolver会从classpath下查找sql文件。查询通过Scanner类实现,Location类指定查询路径,sql文件的命名规则需要符合V${version}__${name}.sql,规则中的前缀V、后缀.sql、分隔符__均在FlywayConfiguration接口中定义。另一种实现类JdbcMigrationResolver会从classpath下查找实现JdbcMigration接口的类,类的命名规则需要符合V${version}__${name}。需要扩展自己的实现类,可以继承BaseMigrationResolver。

MetaDataTable接口负责查找已执行的数据库迁移脚本,方法为findAppliedMigrations(),已执行的数据库迁移脚本用AppliedMigration对象表示。MetaDataTable只有一种实现类MetaDataTableImpl,从数据库schema_version表查询所有记录。

ResolvedMigration集合包含了已经执行的AppliedMigration集合,在执行ResolvedMigration前,需要对比AppliedMigration,找到已执行和未执行的ResolvedMigration,对比通过MigrationInfoServiceImpl.refresh()实现。已执行的ResolvedMigration需要校验文件有没有发生变化,有变更则提示错误。未执行的ResolvedMigration依次执行,执行结果记录在schema_version表中。

Flyway的主要方法:

  1. public class Flyway {
  2. /**数据库迁移*/
  3. public int migrate();
  4. /**校验已执行的迁移操作的变更情况*/
  5. public void validate();
  6. /**清理数据库*/
  7. public void clean();
  8. /**设置数据库的基准版本*/
  9. public void baseline();
  10. /**删除执行错误的迁移记录*/
  11. public void repair();
  12. /**准备执行环境,并执行Command操作,以上方法都调用了execute()来执行操作*/
  13. <T> T execute(Command<T> command);
  14. }

接下来我们分析Flyway.migrate()代码执行的主逻辑。

  1. public void migrate() {
  2. //由Flyway.execute()准备Command.execute()执行所需要的参数
  3. return execute(new Command<Integer>() {
  4. public Integer execute(Connection connectionMetaDataTable,
  5. MigrationResolver migrationResolver, MetaDataTable metaDataTable,
  6. DbSupport dbSupport, Schema[] schemas, FlywayCallback[] flywayCallbacks) {
  7. //为了简化代码,忽略参数传递
  8. doMigrate();
  9. }
  10. });

  11. }

  12. private void doMigrate() {

  13. //校验已执行的迁移操作的变更情况

  14. if (validateOnMigrate) {

  15. doValidate(connectionMetaDataTable, dbSupport, migrationResolver,

  16. metaDataTable, schemas, flywayCallbacks, true);

  17. }

  18. //如果尚未进行数据迁移,即schema_version表中不存在数据,

  19. //并且数据库不为空,则插入一条baseline信息

  20. if (!metaDataTable.exists()) {

  21. //数据库不为空

  22. if (!nonEmptySchemas.isEmpty()) {

  23. //插入一条baseline信息

  24. new DbBaseline(connectionMetaDataTable, dbSupport, metaDataTable,

  25. schemas[0], baselineVersion, baselineDescription, flywayCallbacks).baseline();

  26. }

  27. }

  28. //进行数据迁移

  29. DbMigrate dbMigrate = new DbMigrate(connectionUserObjects, dbSupport,

  30. metaDataTable,schemas[0], migrationResolver, ignoreFailedFutureMigration,

  31. Flyway.this);

  32. return dbMigrate.migrate();

  33. }

接下来看DbMigrate.migrate()的代码片段。

MigrationInfoServiceImpl对比ResolvedMigration和AppliedMigration对象,找出需要执行数据库迁移脚本,通过pending()方法返回。最后执行数据库迁移脚本。

  1. public int migrate() {
  2. int migrationSuccessCount = 0;
  3. while (true) {
  4. int count = metaDataTable.lock(new Callable<Integer>() {
  5.   <span class="hljs-meta">@Override</span>
  6.   <span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">call</span><span class="hljs-params">()</span> </span>{
  7.     <span class="hljs-comment">//为了简化代码,忽略参数传递</span>
  8.     <span class="hljs-keyword">return</span> doMigrate();
  9.   }
  10. }
  11. <span class="hljs-keyword">if</span> (count == <span class="hljs-number">0</span>) {
  12.   <span class="hljs-comment">// No further migrations available</span>
  13.   <span class="hljs-keyword">break</span>;
  14. }
  15. migrationSuccessCount += count;
  16. }

  17. return migrationSuccessCount;

  18. }

  19. private int doMigrate() {

  20. //收集已经入库的数据库迁移记录,和以文件形式存在的数据库迁移脚本

  21. MigrationInfoServiceImpl infoService = new MigrationInfoServiceImpl(

  22. migrationResolver, metaDataTable, configuration.getTarget(),

  23. configuration.isOutOfOrder(), true, true, true);

  24. infoService.refresh();

  25. //infoService.pending()记录将要执行的数据库迁移脚本

  26. LinkedHashMap<MigrationInfoImpl, Boolean> group =

  27. groupMigrationInfo(infoService.pending());

  28. if (!group.isEmpty()) {

  29. //执行数据库迁移操作

  30. applyMigrations(group);

  31. }

  32. }

DbMigrate.doMigrateGroup() 执行数据库迁移脚本

  1. private void doMigrateGroup() {
  2. //执行迁移脚本
  3. migration.getResolvedMigration().getExecutor().execute(connectionUserObjects);
  4. //存入数据库

  5. AppliedMigration appliedMigration = new AppliedMigration(migration.getVersion(),

  6. migration.getDescription(), migration.getType(), migration.getScript(),

  7. migration.getResolvedMigration().getChecksum(), executionTime, true);

  8. metaDataTable.addAppliedMigration(appliedMigration);

  9. }

扩展练习

在一个已经上线的游戏项目中引入Flyway框架,对于已经存在的游戏服,只执行新增的sql语句,对于新搭建的游戏服,需要创建数据库,并执行新增的sql语句。

在这个需求中,需要做的是对于新搭建的游戏服,需要创建数据库。我们可以通过FlywayCallback实现这一点,如果指定数据库为空,则执行初始化数据库的语句。

代码如下:

  1. public static void main(String[] args) {
  2. final Flyway flyway = new Flyway();
  3. flyway.setDataSource("jdbc:mysql://localhost/test", "root", "root");
  4. FlywayCallback flywayCallback = <span class="hljs-keyword">new</span> BaseFlywayCallback() {
  5.     <span class="hljs-meta">@Override</span>
  6.     <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">beforeMigrate</span><span class="hljs-params">(Connection connection)</span> </span>{
  7.         DbSupport dbSupport = DbSupportFactory.createDbSupport(connection, <span class="hljs-keyword">false</span>);
  8.         <span class="hljs-keyword">if</span>(!hasTable(dbSupport)) {
  9.             initDb(dbSupport);
  10.         }
  11.     }
  12.     <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">hasTable</span><span class="hljs-params">(DbSupport dbSupport)</span> </span>{
  13.         Schema&lt;?&gt; schema = dbSupport.getOriginalSchema();
  14.         Table[] tables = schema.allTables();
  15.         <span class="hljs-keyword">if</span>(tables == <span class="hljs-keyword">null</span> || tables.length == <span class="hljs-number">0</span>) {
  16.             <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
  17.         }
  18.         <span class="hljs-comment">//忽略表 schema_version</span>
  19.         <span class="hljs-keyword">if</span>(tables.length == <span class="hljs-number">1</span> &amp;&amp;
  20.             tables[<span class="hljs-number">0</span>].getName().equalsIgnoreCase(<span class="hljs-string">"schema_version"</span>)) {
  21.             <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
  22.         }
  23.         <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
  24.     }
  25.     <span class="hljs-function"><span class="hljs-keyword">private</span>  <span class="hljs-keyword">void</span> <span class="hljs-title">initDb</span><span class="hljs-params">(DbSupport dbSupport)</span> </span>{
  26.         Scanner scanner = <span class="hljs-keyword">new</span> Scanner(
  27.           Thread.currentThread().getContextClassLoader());
  28.         Resource[] resources = scanner.scanForResources(
  29.           <span class="hljs-keyword">new</span> Location(<span class="hljs-string">"db/init"</span>), <span class="hljs-string">""</span>, <span class="hljs-string">".sql"</span>);
  30.         <span class="hljs-keyword">if</span>(resources == <span class="hljs-keyword">null</span> || resources.length == <span class="hljs-number">0</span>) {
  31.             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(
  32.               <span class="hljs-string">"db/init/*.sql not found in the classpath. "</span>);
  33.         }
  34.         <span class="hljs-keyword">for</span>(Resource resource : resources) {
  35.           SqlMigrationExecutor executor = <span class="hljs-keyword">new</span> SqlMigrationExecutor(
  36.               dbSupport, resource, PlaceholderReplacer.NO_PLACEHOLDERS, flyway);
  37.           executor.execute(dbSupport.getJdbcTemplate().getConnection());
  38.         }
  39.     }
  40. };
  41. List&lt;FlywayCallback&gt; callbacks = <span class="hljs-keyword">new</span> ArrayList&lt;FlywayCallback&gt;(
  42.   Arrays.asList(flyway.getCallbacks()));
  43. callbacks.add(flywayCallback);
  44. flyway.setCallbacks(callbacks.toArray(<span class="hljs-keyword">new</span> FlywayCallback[callbacks.size()]));
  45. flyway.setBaselineOnMigrate(<span class="hljs-keyword">true</span>);
  46. flyway.repair();
  47. flyway.migrate();
  48. }

  1. </div>

数据库迁移框架Flyway介绍的更多相关文章

  1. Java敏捷数据库迁移框架——Flyway

    1.引言 想到要管理数据库的版本,是在实际产品中遇到问题后想到的一种解决方案,当时各个环境的数据库乱作一团,没有任何一个人(开发.测试.维护人员)能够讲清楚当前环境下的数据库是哪个版本,与哪个版本的应 ...

  2. 195. Spring Boot 2.0数据库迁移:Flyway

    [视频&交流平台] àSpringBoot视频:http://t.cn/R3QepWG à SpringCloud视频:http://t.cn/R3QeRZc à Spring Boot源码: ...

  3. 数据库迁移神器——Flyway

    不知道你有没有遇到过这种场景,一套代码部署在不同的环境中,随着时间的过去,各个环境代码有版本差异,代码层面可以通过不同的版本来控制,但是数据库层面经常容易忘记更新! 前言 比如刚开始环境 A 和环境 ...

  4. 使用FluentMigrator进行数据库迁移

    介绍 在开发的过程中,经常会遇到数据库结构变动(表新增.删除,表列新增.修改.删除等).开发环境.测试环境.正式环境都要记性同步:如果你使用EF有自动迁移的功能,还是挺方便的.如果非EF我们需要手工处 ...

  5. 细说flask数据库迁移

    什么情况下要用数据库迁移? 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库.最直接的方式就是删除旧表,但这样会丢失数据. 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化 ...

  6. Flask从入门到精通之使用Flask-Migrate实现数据库迁移

    在开发程序的过程中,你会发现有时需要修改数据库模型,而且修改之后还需要更新数据库.仅当数据库表不存在时,Flask-SQLAlchemy 才会根据模型进行创建.因此,更新表的唯一方式就是先删除旧表,不 ...

  7. Flask 数据库迁移

    在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库.最直接的方式就是删除旧表,但这样会丢失数据. 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中. ...

  8. Flask项目中数据库迁移的使用

    数据库迁移 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库.最直接的方式就是删除旧表,但这样会丢失数据. 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用 ...

  9. Flyway:数据库版本迁移工具的介绍

    目录 Flyway介绍 Flyway的工作模式 Flyway的使用场景 命令行 使用Maven或Gradle插件 migrate clean info validate baseline Java A ...

随机推荐

  1. C++ 学习路线推荐

        相信有非常大一部分学计算机的童鞋都是靠自学,即使本身是计算机专业的同学,也会认为只通过课堂上的学习是远远不够的,并且在上课时所用到的教材也不够好.然而自学的时候有个非常大的问题就是找不到合适的 ...

  2. 【Codeforces Round #431 (Div. 2) C】From Y to Y

    [链接]点击打开链接 [题意] 让你构造一个大小最多为10W的字符multiset. 你进行n-1次操作; 每次操作,从set中取出两个字符串,一开始单个字符被认为是字符串. 然后把它们连接在一起. ...

  3. valueof(), intvalue(0 parseint() 这三个方法怎么用

    valueOf(int i) 返回一个表示指定的 int 值的 Integer 实例.valueOf(String s) 返回保存指定的 String 的值的 Integer 对象.valueOf(S ...

  4. [D3] Animate Chart Axis Transitions in D3 v4

    When the data being rendered by a chart changes, sometimes it necessitates a change to the scales an ...

  5. win8.1 “服务器运行失败”的解决方法

    平台:win8.1 SP1 问题:安装QQ安全管家又卸载后出现了奇怪的问题,1.在桌面点右键→个性化时,提示“服务器运行失败”.2.右键点击“这台电脑”,选择“属性”时没有反应.3.开始屏幕里随便选择 ...

  6. HttpWatch--time chart分析

    这是一个IE的插件,下载可以点这里.下载后解压如下图所示,一共有4个文件.HttpWatch Professional是单独软件,可以单独使用. 解压后有四个文件 插件安装时,只需运行httpwatc ...

  7. 原生js大总结一

    001.浅谈堆和栈的理解?   js变量存储有栈存储和堆存储,基本数据类型的变量存储在栈中,引用数据类型的变量存储在堆中 引用类型数据的地址也存在栈中   当访问基础类型变量时,直接从栈中取值.当访问 ...

  8. 详解Spring Boot配置文件之多环境配置

    一. 多环境配置的好处: 1.不同环境配置可以配置不同的参数~ 2.便于部署,提高效率,减少出错~ 二. properties多环境配置 1. 配置激活选项 spring.profiles.activ ...

  9. ARCGIS动态画点

    小马哥淡定 原文 ARCGIS动态画点 private void DrawPointOnMap(double x, double y,bool clear) { IMapControl2 pMapCt ...

  10. 【重拾Effective Java】一

    之前看这本<Effective Java(第二版)>都是非常早曾经了.这本书确实是本好书.须要细嚼慢咽,每次看都有不同的体验. 在此写博客巩固一下. 第一章.创建和销毁对象 考虑用静态工厂 ...