官方文档

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

入门示例

Java代码

package foobar;

import org.flywaydb.core.Flyway;

public class App {

public static void main(String[] args) {

Flyway flyway = new Flyway();

// 指定数据源

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

// 开始数据迁移

flyway.migrate();

}

}

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

create table PERSON (
ID int not null,
NAME varchar(100) not null
);

运行程序,在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的主要方法:

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

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

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

});

}

private void doMigrate() {

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

if (validateOnMigrate) {

doValidate(connectionMetaDataTable, dbSupport, migrationResolver,

metaDataTable, schemas, flywayCallbacks, true);

}

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

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

if (!metaDataTable.exists()) {

//数据库不为空

if (!nonEmptySchemas.isEmpty()) {

//插入一条baseline信息

new DbBaseline(connectionMetaDataTable, dbSupport, metaDataTable,

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

}

}

//进行数据迁移

DbMigrate dbMigrate = new DbMigrate(connectionUserObjects, dbSupport,

metaDataTable,schemas[0], migrationResolver, ignoreFailedFutureMigration,

Flyway.this);

return dbMigrate.migrate();

}

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

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

public int migrate() {
int migrationSuccessCount = 0;
while (true) {
int count = metaDataTable.lock(new Callable<Integer>() {
  <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Integer <span class="hljs-title">call</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">//为了简化代码,忽略参数传递</span>
<span class="hljs-keyword">return</span> doMigrate();
}
}
<span class="hljs-keyword">if</span> (count == <span class="hljs-number">0</span>) {
<span class="hljs-comment">// No further migrations available</span>
<span class="hljs-keyword">break</span>;
}
migrationSuccessCount += count;

}

return migrationSuccessCount;

}

private int doMigrate() {

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

MigrationInfoServiceImpl infoService = new MigrationInfoServiceImpl(

migrationResolver, metaDataTable, configuration.getTarget(),

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

infoService.refresh();

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

LinkedHashMap<MigrationInfoImpl, Boolean> group =

groupMigrationInfo(infoService.pending());

if (!group.isEmpty()) {

//执行数据库迁移操作

applyMigrations(group);

}

}

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

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

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

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

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

metaDataTable.addAppliedMigration(appliedMigration);

}

扩展练习

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

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

代码如下:

public static void main(String[] args) {
final Flyway flyway = new Flyway();
flyway.setDataSource("jdbc:mysql://localhost/test", "root", "root");
FlywayCallback flywayCallback = <span class="hljs-keyword">new</span> BaseFlywayCallback() {

    <span class="hljs-meta">@Override</span>
<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>{
DbSupport dbSupport = DbSupportFactory.createDbSupport(connection, <span class="hljs-keyword">false</span>);
<span class="hljs-keyword">if</span>(!hasTable(dbSupport)) {
initDb(dbSupport);
}
} <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>{
Schema&lt;?&gt; schema = dbSupport.getOriginalSchema();
Table[] tables = schema.allTables();
<span class="hljs-keyword">if</span>(tables == <span class="hljs-keyword">null</span> || tables.length == <span class="hljs-number">0</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}
<span class="hljs-comment">//忽略表 schema_version</span>
<span class="hljs-keyword">if</span>(tables.length == <span class="hljs-number">1</span> &amp;&amp;
tables[<span class="hljs-number">0</span>].getName().equalsIgnoreCase(<span class="hljs-string">"schema_version"</span>)) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
} <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>{
Scanner scanner = <span class="hljs-keyword">new</span> Scanner(
Thread.currentThread().getContextClassLoader());
Resource[] resources = scanner.scanForResources(
<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>);
<span class="hljs-keyword">if</span>(resources == <span class="hljs-keyword">null</span> || resources.length == <span class="hljs-number">0</span>) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(
<span class="hljs-string">"db/init/*.sql not found in the classpath. "</span>);
}
<span class="hljs-keyword">for</span>(Resource resource : resources) {
SqlMigrationExecutor executor = <span class="hljs-keyword">new</span> SqlMigrationExecutor(
dbSupport, resource, PlaceholderReplacer.NO_PLACEHOLDERS, flyway);
executor.execute(dbSupport.getJdbcTemplate().getConnection());
}
}
}; List&lt;FlywayCallback&gt; callbacks = <span class="hljs-keyword">new</span> ArrayList&lt;FlywayCallback&gt;(
Arrays.asList(flyway.getCallbacks()));
callbacks.add(flywayCallback);
flyway.setCallbacks(callbacks.toArray(<span class="hljs-keyword">new</span> FlywayCallback[callbacks.size()])); flyway.setBaselineOnMigrate(<span class="hljs-keyword">true</span>);
flyway.repair();
flyway.migrate();

}

      </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. SQL查询练习二(From LeetCode)

    请选用MySQL进行测试. 1.将男性和女性的工资互换(E) 思路:使用case when进行条件判断,在使用update进行修改 update salary set sex = case sex w ...

  2. 2. Dubbo和Zookeeper的关系

    转自:https://www.cnblogs.com/hirampeng/p/9540243.html Dubbo建议使用Zookeeper作为服务的注册中心. 1.   Zookeeper的作用: ...

  3. HTML基础第十一讲---背景标志

    转自:https://i.cnblogs.com/posts?categoryid=1121494 您是否老觉得网页「空空的」,没错!一个可能是我们还没有很多内容,另一个可能则是我们还没有设定网页背景 ...

  4. TeamViewer的下载、安装和使用(windows7、CentOS6.5和Ubuntu14.04(64bit))(图文详解)

    不多说,直接上干货! TeamViewr是远程支持.远程访问.在线协作和会议软件. 分为从windows7.CentOS6.5和Ubuntu14.04(64bit) 系统来详解下载.安装和初步使用! ...

  5. Android Studio com.android.dex.DexException: Multiple dex files define(重复引用包)

    如果你用Android Studio开发,并且要用其他项目作为library,这个问题是很容易出现的.出现这个问题的原因是包的重复引用,意思就是在你自己的项目中引用了某个包,而被你作为library的 ...

  6. visual studio code 中 debugger for chrome 插件的配置

    安装 debugger for chrome 插件后,把默认的 launch.json 改成: { "name": "谷歌浏览器", "type&qu ...

  7. iOS 之应用性能调优的25个建议和技巧

    注意:每在优化代码之前,你都要注意一个问题,不要养成"预优化"代码的错误习惯. 时常使用Instruments去profile你的代码来发现须要提升的方面.Matt Gallowa ...

  8. Java核心技术 卷Ⅰ 基础知识(7)

    第13章 集合 集合接口 具体的集合 在表中,除了Map结尾的类之外,其他类都实现了Collection接口,而以Map结尾的类实现了Map接口. 链表 数组列表 散列集 树集 双端队列 优先级队列 ...

  9. DC针对pipeline的优化

    set_optimize_register    true compile  -ultra 调整pipleline各级的组合逻辑,使得各级组合逻辑的延迟跟接近 对非pipeline进行优化: regi ...

  10. Android 调用系统邮件,发送邮件到指定邮箱

    在项目中,最后有一个联络我们,要求是点击号码还有邮箱地址能够发送邮件,这时候解决的方案其实有两种,一种是调用系统发邮件的软件,可以添加邮箱账号就可以发送邮件:第二种是使用javamail来发送邮件.在 ...