SpringBoot × Flyway３パターン（FlywayAutoConfiguration/Java API/Mavenプラグイン）

# まえがき
SpringBootでFlywayを利用する方法は３つある。
・FlywayAutoConfiguration経由
・Java API経由
・Mavenプラグイン経由

本記事では３つの設定方法を備忘録として残しておくものである。
# FlywayAutoConfiguration経由
**application.propertiesに必要なプロパティと、pom.xmlに依存性を定義するだけで、**
**SpringBootアプリ起動時にmigrationを行うことができる。**
後述するJava API、Mavenプラグインの方法と比べて、設定するものが少ないのですぐ使うには一番良い方法。

application.properties
```properties
# DB
spring.datasource.username=${db_username}
spring.datasource.password=${db_password}
spring.datasource.url=jdbc:postgresql://${host}:${port}/${database}

# Flyway
spring.flyway.enabled=true
spring.flyway.user=${db_username}
spring.flyway.password=${db_password}
spring.flyway.url=${spring.datasource.url}
```

pom.xml
```xml
<depedencies>

   <!-- DBドライバ -->
   <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.3.3</version>
   </dependency>

   <!-- Spring JDBC -->
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.17</version>
    </dependency>

   <!--Flyway -->
    <dependency>
       <groupId>org.flywaydb</groupId>
       <artifactId>flyway-core</artifactId>
       <version>8.5.4</version>
     </dependency>

</depedencies>
```

## ハマったところ
この方法で正常動作するまでにハマったポイントがいくつかあったので、備忘録として残しておく。
### spring.flyway.enabled=true設定してもmigrationが実行されない。なんで？
ぐぐるとSpringBoot×Flywayの記事が沢山出てくるが、「sping.flyway.enabled=trueを設定すればSpringBoot起動時にmigration動くで」くらいしか書いてなくて「いや、動かんわ(´_ゝ｀)」となった。


↑のStackOverFlowにあるように、こういうときはログを出そう。
ということでapplication.propertiesに`logging.level.root=debug`を追加してもう一回SpringBoot起動すると、↓のログが出力された。どうやら設定が足りていないらしい。
```
FlywayAutoConfiguration:
  Did not match:
    - AnyNestedCondition 0 matched 2 did not; NestedCondition on FlywayAutoConfiguration.FlywayDataSourceCondition.FlywayUrlCondition @ConditionalOnProperty (spring.flyway.url) did not find property 'url'; NestedCondition on FlywayAutoConfiguration.FlywayDataSourceCondition.DataSourceBeanCondition @ConditionalOnBean (types: javax.sql.DataSource; SearchStrategy: all) did not find any beans of type javax.sql.DataSource (FlywayAutoConfiguration.FlywayDataSourceCondition)
  Matched:
    - @ConditionalOnClass found required class 'org.flywaydb.core.Flyway' (OnClassCondition)
    - @ConditionalOnProperty (spring.flyway.enabled) matched (OnPropertyCondition)

FlywayAutoConfiguration.FlywayConfiguration:
  Did not match:
    - @ConditionalOnClass did not find required class 'org.springframework.jdbc.support.JdbcUtils' (OnClassCondition)
    - Ancestor org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration did not match (ConditionEvaluationReport.AncestorsMatchedCondition)
```
FlywayAutoConfigurationクラスを有効にするための条件がいくつかあり、↑のログに出ている「Did not match」のところを解消すればいけそう。

足りてないやつその１
```
@ConditionalOnClass did not find required class 'org.springframework.jdbc.support.JdbcUtils'
```
クラスパスにJdbcUtilsクラスがないということで、JdbcUtilsクラスを含む依存性を追加してあげる。
```xml
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.3.17</version>
</dependency>
```

足りてないやつその２
```
AnyNestedCondition 0 matched 2 did not; NestedCondition on FlywayAutoConfiguration.FlywayDataSourceCondition.FlywayUrlCondition @ConditionalOnProperty (spring.flyway.url) did not find property 'url';
```
application.propertiesに`spring.flyway.url`を追加してあげる。
```propertiesに
# DB
spring.datasource.username=${db_username}
spring.datasource.password=${db_password}
spring.datasource.url=jdbc:postgresql://${host}:${port}/${database}

# Flyway
spring.flyway.enabled=true
spring.flyway.url=${spring.datasource.url} ←追加
```
再度SpringBootを起動。しかし失敗。まだ何か足りていないようだ。
```
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.internal.exception.FlywaySqlException: Unable to obtain connection from database: The server requested SCRAM-based authentication, but the password is an empty string.
--------------------------------------------------------------------------------------------------------------------------------
SQL State  : 08004
Error Code : 0
Message    : The server requested SCRAM-based authentication, but the password is an empty string.
```
どうやらFlywayAutoConfigureクラスは有効化できたようで、FlywayInitializerのBean生成時にエラーが発生している。「the password is an empty string」って怒られているので、`spring.flyway.password`をapplication.propertiesに追加してあげる。（あとspring.flyway.userも）
```propertiesに
# DB
spring.datasource.username=${db_username}
spring.datasource.password=${db_password}
spring.datasource.url=jdbc:postgresql://${host}:${port}/${database}

# Flyway
spring.flyway.enabled=true
spring.flyway.user=${db_username} ←追加
spring.flyway.password=${db_password} ←追加
spring.flyway.url=${spring.datasource.url}
```
これで`mvn spring-boot:run`実行時に動くようになった！
### AutoConfigureが動作する仕組み
先述した`FlywayAutoConfigurationクラスを有効にするための条件がいくつかあり`の部分だが、SpringBootはこんな感じで「**この条件が満たされていたら、HogeAutoConfigurationを有効化する**」みたいなのが沢山ある。↓の記事に分かりやすくまとまっている。

https://qiita.com/kazuki43zoo/items/8645d9765edd11c6f1dd

FlywayAutoConfigurationはそのうちの１つである。各AutoConfigurationクラスを有効化する条件はSpringBootソースコードを見に行けば分かる。FlywayAutoConfigurationだと↓のように、クラス上部にある@Conditionalらへんが条件にあたる。
```java
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
		HibernateJpaAutoConfiguration.class })
@ConditionalOnClass(Flyway.class)
@Conditional(FlywayDataSourceCondition.class)
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
@Import(DatabaseInitializationDependencyConfigurer.class)
public class FlywayAutoConfiguration {
```
https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java


# Java API経由

## pom.xml

```xml
<!-- Flyway Java API -->
<dependency>
　　<groupId>org.flywaydb</groupId>
　　<artifactId>flyway-core</artifactId>
　　<version>8.0.1</version>
</dependency>

<!-- Flyway Java APIで使うDBドライバ -->
<dependency>
　　<groupId>org.postgresql</groupId>
　　<artifactId>postgresql</artifactId>
　　<version>42.2.23</version>
</dependency>
```

## Mainクラス

```java
public static void main(String[] args) {
  // appliacation.propertiesに外だしした方がgood
  String url = "jdbc:postgresql://localhost:5433/sampleDatabase";
  String user = "postgres";
  String password = "postgres";
  Flyway flyway = Flyway.configure().dataSource(url, user, password).load();
  flyway.migrate();
}
```

## SQLファイル用意(migrateで実行されるSQL)
・【src/main/resources/db/migration】配下にSQLファイル配置
・ファイル名は「V{数字}__HogeHoge.sql」
　※アンダースコアは２つ。１つだけだと✕

```
# V1 → V2の順番で実行される
src/main/resources/db/migration/V1__Initialize.sql(テーブル定義用)
src/main/resources/db/migration/V2__insertData.sql(データ投入用)
```

### プロファイルでSQLファイルを分ける。
application.properties
```properties
spring.flyway.locations=classpath:/common/db/migration,classpath:/dev/db/migration
```
SQLファイル
```
src/main/resources/common
・V1__Initialize.sql
src/main/resources/dev
・V2__TestData.sql
```

**本番環境ではV1__Initialize.sqlだけ実行したい場合は、プロパティ「spring.flyway.locations」をビルド時に切り替えればOK。**


## Mainクラス実行

・MavenコマンドでJavaクラスを実行させたい場合、exec-maven-plugin経由でMainクラスを実行させる

　実行コマンド：mvn exec:java

```xml
<build>
　　<plugins>
　　　　<plugin>
　　　　　　<groupId>org.codehaus.mojo</groupId>
　　　　　　<artifactId>exec-maven-plugin</artifactId>
　　　　　　<version>1.6.0</version>
　　　　　　<configuration>
　　　　　　　　<mainClass>com.example.Main</mainClass>
　　　　　　</configuration>
　　　　</plugin>
　　</plugins>
</build>
```

---
# Mavenプラグイン経由

## pom.xml

```xml
<build>
　　<plugins>
　　　　<plugin>
　　　　　　<groupId>org.flywaydb</groupId>
　　　　　　<artifactId>flyway-maven-plugin</artifactId>
　　　　　　<version>8.0.1</version>
　　　　　　<!-- DB接続情報 -->
　　　　　　<configuration>
　　　　　　　　<url>jdbc:postgresql://localhost:5433/sampleDatabase</url>
　　　　　　　　<user>postgres</user>
　　　　　　　　<password>postgres</password>
　　　　　　</configuration>
　　　　　　<!-- 必要なDBドライバを定義する  -->
　　　　　　<dependencies>
　　　　　　　　<dependency>
　　　　　　　　　　<groupId>org.postgresql</groupId>
　　　　　　　　　　<artifactId>postgresql</artifactId>
　　　　　　　　　　<version>42.2.23</version>
　　　　　　　　</dependency>
　　　　　　</dependencies>
　　　　</plugin>
　　　</plugins>
</build>
```

## SQLファイル用意(migrateで実行されるSQL)

　※Java API実行時と同じく【src/main/resources/db/migration】配下にSQLファイル配置

## Mavenコマンド実行

```
mvn flyway:migrate
```