Статьи

Объединение возможностей Gradle, Flyway и JOOQ для бесшовной разработки с базами данных

Привет, в этом посте я покажу вам, как легко вы можете объединить мощь Gradle с Flyway и JOOQ. В конце у вас будет рецепт сборки, который автоматически обновляет модели при каждом обновлении базы данных.

Эта проблема

При разработке приложений с доступом к базе данных мы обычно сталкиваемся с проблемой: нам нужно адаптировать наш код к изменениям в схеме базы данных или наоборот.

Поскольку мы стараемся не повторять что-то, отличной идеей будет то, что мы можем создать одно из обоих из другого. Например, генерация доступа к базе данных и перенос объектов из фактической схемы базы данных. Поскольку база данных должна быть источником правды для данных схемы, пример выглядит как хороший путь. Но об этом позже.

Другая проблема заключается в соединении с базой данных для генерации классов из схемы. Создание схемы на лету до того, как класс начнет работать, похоже на привлекательную идею. Это освободило бы разработчика от необходимости иметь соединение с базой данных, когда он восстанавливает классы доступа. Это также было бы удобно в том смысле, что он может легко загрузить окружение на своей локальной машине.

Давайте посмотрим, как мы можем приступить к этим требованиям / задачам.

Инструменты

Решением для второй задачи может быть Flyway , поскольку это умный инструмент для управления и выполнения обновлений схемы для многих реляционных баз данных. Цикл разработки с Flyway (очень кратко) описывается следующим образом: определите изменение схемы в файле SQL, загрузите приложение, и flyway позаботится об этом.

Нет, нам просто нужен генератор от данных схемы до объектов доступа / передачи данных. Я нахожу здесь JOOQ очень удобным, так как это библиотека, которая предоставляет оболочку для доступа к реляционной базе данных через мощный DSL, где SQL-код виден в вашем коде и не скрыт за каким-либо OR-mapper.

JOOQ приходит вместе с необходимым генератором кода, так что нам не нужно писать и изменять неприятный и иногда громоздкий код снова и снова.

Теперь, когда мы сделали выбор технологий / библиотек, давайте перейдем к

Gradle интеграция

Я представлю новое задание Gradle generateJOOQ. Он позаботится о переносе / создании схемы с помощью Flyway и генерации кода с помощью JOOQ и должен выполняться перед каждым запуском compileJava .

Начнем с необходимых конфигураций и исходных наборов. Классы будут сгенерированы в сгенерированный исходный набор, который требует наличия следующего фрагмента в файле сборки:

01
02
03
04
05
06
07
08
09
10
configurations {
    compile.extendsFrom generatedCompile
}
  
sourceSets {
    generated
    main {
        compileClasspath += generated.output
    }
}

Следующим шагом теперь является информирование Gradle о Flyway и JOOQ путем добавления необходимых зависимостей в блок buildscript нашего build.gradle.

01
02
03
04
05
06
07
08
09
10
11
buildscript {
    repositories {
        mavenCentral()
    }
  
    dependencies {
        classpath 'org.jooq:jooq-codegen:3.6.1'
        classpath 'com.h2database:h2:1.4.188'
        classpath 'org.flywaydb:flyway-core:3.2.1'
    }
}

Для этого примера выбрана база данных H2, но вы можете использовать любой драйвер базы данных, который можно использовать вместе с Flyway и JOOQ.

Теперь мы должны предоставить конфигурацию для JOOQ, чтобы он мог генерировать классы в нужную папку и путь к классам

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
def writer = new StringWriter()
new groovy.xml.MarkupBuilder(writer)
        .configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.6.0.xsd') {
    jdbc() {
        driver('org.h2.Driver')
        url("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}")
        user('sa')
        password('')
    }
    generator() {
        database() {
        }
        generate() {
        }
        target() {
            packageName('com.coderskitchen.example')
            directory('src/generated/java')
        }
    }
}

В качестве следующего шага мы можем создать задачу generateJOOQ :

01
02
03
04
05
06
07
08
09
10
11
12
13
import org.flywaydb.core.Flyway
  
task generateJOOQ() {
    doLast {
        def Flyway flyway = new Flyway();
        flyway.setDataSource("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}", "sa", null);
        flyway.setLocations("filesystem:${project.projectDir}/src/main/resources/db/migration")
        flyway.migrate();
        org.jooq.util.GenerationTool.generate(
                javax.xml.bind.JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
        )
    }
}

Как видите, файлы миграции взяты из src / main / resources / db /igration . Это имеет то преимущество, что, если нам нравится / нужно, может доставлять файлы SQL переноса в нашем приложении, так что мы можем переносить базу данных непосредственно в рабочую / промежуточную среду. Это очень полезно, если среда не находится под нашим контролем, например, когда приложение работает на компьютерах внешних клиентов.

Зависимости между задачами Gradle будут следующие: compileJava -> compileGeneratedJava -> generateJOOQ && clean :

1
2
3
compileGeneratedJava.dependsOn clean
compileGeneratedJava.dependsOn generateJOOQ
compileJava.dependsOn compileGeneratedJava

Наконец, мы должны добавить зависимости JOOQ к зависимостям в сгенерированную конфигурацию Compile, иначе наш проект не будет построен:

1
2
3
4
5
dependencies {
    generatedCompile 'org.jooq:jooq:3.6.1',
            'org.jooq:jooq-meta:3.6.1',
            'org.jooq:jooq-codegen:3.6.1'
}

Весь минимальный файл сборки выглядит так

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
buildscript {
    repositories {
        mavenCentral()
    }
  
    dependencies {
        classpath 'org.jooq:jooq-codegen:3.6.1'
        classpath 'com.h2database:h2:1.4.188'
        classpath 'org.flywaydb:flyway-core:3.2.1'
    }
}
  
import org.flywaydb.core.Flyway
  
group 'com.coderskitchen'
version '1.0.0'
  
apply plugin: 'java'
apply plugin: 'idea'
  
sourceCompatibility = 1.8
  
repositories {
    mavenCentral()
}
  
configurations {
    compile.extendsFrom generatedCompile
}
  
sourceSets {
    generated
    main {
        compileClasspath += generated.output
    }
}
  
dependencies {
    generatedCompile 'org.jooq:jooq:3.6.1',
        'org.jooq:jooq-meta:3.6.1',
        'org.jooq:jooq-codegen:3.6.1'
  
    testCompile group: 'junit', name: 'junit', version: '4.11'
}
  
def writer = new StringWriter()
new groovy.xml.MarkupBuilder(writer)
    .configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.6.0.xsd') {
    jdbc() {
        driver('org.h2.Driver')
        url("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}")
        user('sa')
        password('')
    }
    generator() {
        database() {
        }
        generate() {
        }
        target() {
            packageName('com.coderskitchen.example')
            directory('src/generated/java')
        }
    }
}
  
task generateJOOQ() {
    doLast {
        def Flyway flyway = new Flyway();
        flyway.setDataSource("jdbc:h2:file:${project.projectDir}/build/generator/${project.name}", "sa", null);
        flyway.setLocations("filesystem:${project.projectDir}/src/main/resources/db/migration")
        flyway.migrate();
        org.jooq.util.GenerationTool.generate(
            javax.xml.bind.JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
        )
    }
}
  
compileGeneratedJava.dependsOn clean
compileGeneratedJava.dependsOn generateJOOQ
compileJava.dependsOn compileGeneratedJava

Наконец у нас есть все вместе, чтобы попробовать это. Все? С точки зрения настройки, это правильно, просто отсутствует какой-то пример.

Бегущий пример

Пример о простом календаре парусных гонок, написанном в две итерации

  • первый будет содержать таблицу с датами гонки со всей информацией о месте
  • вторая итерация перенесет эту схему в более нормализованную, где место отделено от даты

Я опубликовал пример на github ( https://github.com/coders-kitchen/gradle-jooq-flyway-example ) и у меня есть два тега, соответствующих упомянутым итерациям.

До свидания и хорошей недели