当前位置: 首页 > 知识库问答 >
问题:

Flyway - SQL迁移-多个git分支上的脚本版本

南宫奇思
2023-03-14

在该项目中,我们使用Flyway迁移工具来开发数据库模式以及这些脚本的git历史。

这个想法是每个脚本编号都以number为前缀,增加值10,然后是名称,例如:

  • 0000_name_one。sql
  • 0010_name_two。sql
  • 0020_name_tree.sql

但是,由于我们必须开始处理多个分支(例如,两个主要分支是:主master_ext),再加上我们有一些用于耗时功能的开发分支 - 因此选择正确的脚本编号存在问题。

当长期开发分支引入很少的更改脚本时,当它合并到主脚本或master_ext时,如果有人也引入了相同数量的不同脚本,我们就会发生冲突。(由于名称不同,合并可能会成功,但飞行路线迁移将失败)

在处理多个分支时,如何维护SQL脚本版本有什么模式或良好实践吗?

提前致谢

共有2个答案

富涛
2023-03-14

您不应将迁移的版本设置为描述部分开始的“__”(双下划线)为止的部分。

迁移的命名方案(根据有关迁移的文档)为:

<flag char><version><separator><description><suffix>

(实际上,其中大部分是可配置的,但假设它没有更改)

  • 标志 char 是以下之一 V(用于版本化迁移)、R(用于可重复迁移)、U(用于撤消迁移)
  • 版本是任何不使用分隔符字符串的内容。通常应用一些数字方案。
  • separator_为“__”(双下划线)
  • 后缀是为迁移定义的任何后缀(例如.sql

由于版本可以是任何东西,因此您可以轻松引入特定于开发人员或分支的组件,并且通过这种方式将有助于确保唯一的名称。

(BTW:根据文档,您在问题中显示的名称不是正确的flyway迁移名称!)

然而:

您仍然面临根据逻辑依赖关系确保有效排序的问题。

幸运的是,在大多数情况下,排序约束只出现在一个开发单元中,通常是在一个分支中。因此,确保所有相关的迁移都按顺序执行将会产生正确的结果。为了在不同的开发单元之间获得有用的排序,您可能只需要回复到时间尺度。

例如,使用以下代码

  • yyyymmdd是开发单位的日期标记(例如开发开始或计划部署到生产的日期)
  • unitseq是标识开发单元的序列号。这需要确保正确的排序!因此,使用一组固定的字符集会有所帮助。
  • subseq是开发单元内的排序序列号。

对于更复杂的情况,需要正确的开发序列排序,您可能仍然需要注意跨开发单元的正确排序。

回到问题中的例子:

由于您的姓名中没有分隔符,以下变体仍然会被认为是不同的:

  • 0000_name_one.sql
  • 0000_name_one_projectA.sql
  • 0000_name_one_project_B.sql

根据我的建议,你可以转向使用:

  • V20180211. p000.0000__name_one.sql
  • V20180302. p001.0000__name_one_projectA.sql
  • V20180301. p002.0000__name_one_projectB.sql

请注意,两个版本的执行顺序不同!

孔逸春
2023-03-14

Flyway 不提供任何构建功能来执行相同的操作。

然而,我已经为我的项目做了这件事,我认为这是迄今为止我们能做的最好的事情。

    < li >将Flyway版本作为实际时间戳,这样无论您创建哪个git分支,它都将始终是唯一且有序的。 < li >编写一个程序来生成此版本,并要求您的所有开发人员使用此文件,以便获得需要使用的下一版本。

以下是我目前使用的示例

package com.demo;


import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class GenerateFileVersion {

    public static void main (String... args){
        GenerateFileVersion f=new GenerateFileVersion();
        f.fileVersion();
    }

    private String trimOrPad(String str, int length, char padChar) {
        String result;
        if (str == null) {
            result = "";
        } else {
            result = str;
        }

        if (result.length() > length) {
            return result.substring(0, length);
        } else {
            while (result.length() < length) {
                result = padChar+result;
            }

            return result;
        }
    }

    private String fileVersion(){
        Date date = new Date();
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("CST6CDT"));
        calendar.setTime(date);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss");

        StringBuffer sb=new StringBuffer();
        sb.append(calendar.get(Calendar.YEAR)%100);
        sb.append(".");
        sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.MONTH)+1),2,'0'));
        sb.append(".");
        sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.DATE)),2,'0'));
        sb.append(".");
        sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.HOUR_OF_DAY)),2,'0'));
        sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.MINUTE)),2,'0'));
        sb.append(this.trimOrPad(String.valueOf(calendar.get(Calendar.SECOND)),2,'0'));

        System.out.println("Choose Your Next File Name From below list...");

        int i=0;

        for(ENVIRONMENT env: ENVIRONMENT.values()){
            System.out.println("Next File Name for Making DDL Change : "+"V"+sb.toString()+this.trimOrPad(String.valueOf(i++),2,'0')+"__"+env.toString()+"_DDL.sql");
            System.out.println("Next File Name for Making DML Change : "+"V"+sb.toString()+this.trimOrPad(String.valueOf(i++),2,'0')+"__"+env.toString()+"_DML.sql");
        }

        return sb.toString();
    }

    private enum ENVIRONMENT{COMMON(1),LOCAL(9),STAGE(4),MTF(5),PERF(7),PROD(2);
        private int value;
        private ENVIRONMENT(int value) { this.value = value; }
    }


}

您还可以向您的项目添加更多功能,以确保所有开发人员不会犯任何错误,您可以将以下程序添加为junit测试用例,以便在有任何文件不符合flyway版本控制标准时构建失败。

样品在下面

package com.demo;

import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootRestApplicationTests {

    @Test
    public void checkDuplicateVersion() {

        System.out.println("Starting checkDuplicateVersion");

        List<ClassLoader> classLoadersList = new LinkedList<>();
        classLoadersList.add(ClasspathHelper.contextClassLoader());
        classLoadersList.add(ClasspathHelper.staticClassLoader());

        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .setScanners(new ResourcesScanner())
                .setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
                .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("local"))));

        Reflections reflections1 = new Reflections(new ConfigurationBuilder()
                .setScanners(new ResourcesScanner())
                .setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
                .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("common"))));

        Stream<String> stream1=  reflections.getStore().get("ResourcesScanner").keys().stream();
        Stream<String> stream2=  reflections1.getStore().get("ResourcesScanner").keys().stream();

        Stream<String> resultingStream=Stream.of(stream1,stream2).flatMap(i -> i);

        //resultingStream.forEach(System.out::println);

        List<String> existingFileVersions=
                resultingStream
                        .filter(f -> f.startsWith("V"))
                        .filter(f -> f.endsWith(".sql"))
                        //.forEach(System.out::println);
                        .map(n -> n.split("__")[0].substring(1))
                        //.forEach(System.out::println);
                        .collect(Collectors.toList());

        Set<String> duplicateVersion=existingFileVersions.stream().filter(i -> Collections.frequency(existingFileVersions, i) >1)
                .collect(Collectors.toSet());

        duplicateVersion.forEach( i -> System.out.println("Duplicate Version found "+i));

        Assert.assertEquals(0,duplicateVersion.size());
    }

    @Test
    public void checkFlywayFileNamingStandard(){

        System.out.println("Starting checkFlywayFileNamingStandard");

        List<ClassLoader> classLoadersList = new LinkedList<>();
        classLoadersList.add(ClasspathHelper.contextClassLoader());
        classLoadersList.add(ClasspathHelper.staticClassLoader());

        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .setScanners(new ResourcesScanner())
                .setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
                .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("local"))));

        Reflections reflections1 = new Reflections(new ConfigurationBuilder()
                .setScanners(new ResourcesScanner())
                .setUrls(ClasspathHelper.forManifest(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))))
                .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("common"))));

        Stream<String> stream1=  reflections.getStore().get("ResourcesScanner").keys().stream();
        Stream<String> stream2=  reflections1.getStore().get("ResourcesScanner").keys().stream();

        Stream<String> resultingStream=Stream.of(stream1,stream2).flatMap(i -> i);
        //resultingStream.forEach(System.out::println);

        resultingStream
                .filter(f -> f.endsWith(".sql"))
                .forEach(n -> {

                    if(!n.split("__")[0].toUpperCase().startsWith("V")){
                        System.out.println("File starts with " + n + " Does not start with Letter V or v. Please fix it.");
                        Assert.fail();
                    }

                    for(String s : n.split("__")[0].substring(1).split("\\.")){
                        try {
                            //System.out.println(n);
                            Integer.valueOf(s);
                        }catch(Exception e){
                            //e.printStackTrace();
                            System.out.println("File starting with "+ n + " does not match flyway standard");
                            System.out.println("Flyway standard is V{version}__{description}.sql");
                            Assert.fail();
                        }
                    }
                });
    }

}

更多细节,你可以在这里看到我的项目

 类似资料:
  • 我想使用 Flyway 获取特定数据库的最新架构版本值。Flyway 中是否有函数可以在命令行中获取当前架构版本号? 我可以运行以下命令: 这为我提供了数据库的整个架构内容(缩短),如下所示: 我只对最后一个架构条目版本“1.5.9”值感兴趣。 我的环境如下: < li>Windows 7 < li>Flyway 3.0

  • 我用试用密钥尝试了State/BaselineMigration功能。https://flywaydb.org/documentation/concepts/baselinemigrationsFlyWay迁移执行脚本并正确移动到正确的版本。但在此基础上运行的所有迁移都失败了 从S开始2__xxx.sql然后它在版本2的模式表中创建一行并键入"SQL_STATE_SCRIPT" 再次执行flywa

  • 我当前的项目有几个Flyway迁移,用于将初始数据导入数据库。这个数据是方便的,特别是对于开发人员能够快速设置项目。生产数据通过一些批处理作业导入,具有较新的版本。 其中有些迁移相当大(~20MB),因此每次应用程序启动时,Flyway都要花费一些时间来计算迁移的校验和。这也是集成测试的一个问题,因为它们也需要更长的时间。 null 我还有什么其他选择?如果可能的话,我希望使用Flyway而不是手

  • 我们希望使用Flyway进行数据库迁移。除了迁移脚本之外,我们还需要执行一些不应被视为迁移的脚本(并且不需要在schema_version表中跟踪)。例如,执行包含配置数据或仅与开发环境相关的测试数据的脚本。我们仍然希望使用flyway-maven-plugin来执行这些SQL脚本,而不是在我的flyway-maven-plugin之前/之后运行的不同maven插件。 对于flyway的迁移前和迁

  • Mercurial(水银)是和Git同时代的、与之齐名的一款著名的分布式版本控制系统,也有相当多的使用者。就像水银又名汞,作为版本控制系统的Mercurial又称作Hg(水银元素符号)。Hg具有简单易用的优点,至少Hg提交的顺序递增的数字编号让Subversion用户感到更为亲切。Hg的开发语言除少部分因性能原因使用C语言外,大部分用Python语言开发完成,因而更易扩展,最终形成了Hg最具特色的

  • Subversion版本库到Git版本库的转换,最好的方法就是git-svn。而git-svn的使用方法在前面“Git和SVN协同模型”一章已经详细介绍过。本章的内容将不再对git-svn的用法做过多的重复,只在这里强调一下版本库迁移时的注意事项,相关git-svn内容还请参照前面的内容。 在迁移之前要确认一个问题,Subversion转换到Git库之后,Subversion还继续使用么?意思是说