Spring × Gradleにおいて[jpa-schema-gradle-plugin]プラグインを用いてDDLを自動生成する

## 概要・背景
SpringBootのJavaアプリにおいて、application.propertiesに`spring.jpa.hibernate.ddl-auto=create-drop`とかDDLに関する設定値を定義すればテーブルを自動生成してくれるけど、
「毎回dropされてデータが初期化されてしまう」とか「移行用とかなんだかんだでDDL必要だよね」っていうシーンでは融通が利かないので、DDLを自動生成するgradleタスクを作成した。

良さげなプラグイン[jpa-schema-gradle-plugin](https://github.com/divinespear/jpa-schema-gradle-plugin)が公開されていたのでこれを利用する。
このプラグインは指定パッケージ配下(packageToScanの設定値)の中でEntityクラスのものを読み込んでDDLを作成してくれる。


## 前提条件
Spring Boot
gradle
(build.gradleの使い方とかは理解しているものとする。)

## 準備

__1. プラグイン呼び出しのアプリ設定__

[jpa-schema-gradle-plugin](https://github.com/divinespear/jpa-schema-gradle-plugin)のHow to Useに従い、必要な設定値を定義していく。
今回自分はDBはOracle、OR mapperはHibernateなので、最終的な最終的な設定値は以下のようになった。

```gradle:build.gradle

plugins {
  id 'io.github.divinespear.jpa-schema-generate' version '0.3.6'
}
dependencies {
  implementation 'com.oracle.database.jdbc:ojdbc8:19.8.0.0'
}
generateSchema {
    vendor = 'hibernate'
    packageToScan = ['com.example.domain']   //自身のgradleプロジェクト または domainのディレクトリを直指定する。
    databaseProductName = 'Oracle12'
    scriptAction = 'drop-and-create'   //drop.sqlとcreate.sqlが作成される。
    properties = [
            'hibernate.dialect': 'org.hibernate.dialect.Oracle12cDialect',
            'hibernate.physical_naming_strategy': 'org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy',    //column nameで"_"が入るようにする。
            'hibernate.implicit_naming_strategy': 'org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy'
    ]
}
```

__2.Entityの用意__
Entityは以下の用にsrc/main/java/com/example/domain/にCustomer.javaとSpringUser.javaを用意した。

```java:Customer.java
package com.example.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "CUSTOMER")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_CUSTOMER_GENERATOR")
    @SequenceGenerator(name = "SEQ_CUSTOMER_GENERATOR", sequenceName = "SEQ_CUSTOMER", allocationSize = 1)
    private Integer id;
    @Column
    private String firstName;
    @Column
    private String lastName;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(nullable = true, name = "USER_NAME")
    private SpringUser user;

}
```

```java:SpringUser.java
package com.example.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;

import javax.persistence.*;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "SPRING_USER")
@ToString(exclude = "customers")    //これはフィールド変数のcustomers
public class SpringUser {
    @Id
    private String userName;
    @JsonIgnore
    private String encodedPassword;
    @JsonIgnore
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
    private List<Customer> customers;
}
```

## 実行
上述のbuild.gradleを作ったら、"Refresh all Gradle Projects"を忘れずに実行する(gradleタスクが追加される)。
ターミナルから`gradle generateSchema`と実行してやれば、build/generated-schema/create.sqlが作成される(デフォルトの出力先)。
<img width="750" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602075/8d62f481-664e-852a-3d5b-d5c497046f81.png">

出来上がったcreate.sqlがこちら。
Customer.javaで定義した通り、SEQUENCEもきちんと反映して作成されている。
カラムの物理名に"_"をいれるかどうかなどは、build.gradleで設定した値"SpringPhysicalNamingStrategy"で変更したりできるので、細かい仕様は、[jpa-schema-gradle-plugin](https://github.com/divinespear/jpa-schema-gradle-plugin)のHow to Useを読めばいいと思います。

```sql:create.sql
create sequence seq_customer start with 1 increment by  1;
create table customer (id number(10,0) not null, first_name varchar2(255 char), last_name varchar2(255 char), user_name varchar2(255 char), primary key (id));
create table spring_user (user_name varchar2(255 char) not null, encoded_password varchar2(255 char), primary key (user_name));
alter table customer add constraint FKc7gvbu1i8l83wyt8q39egdfka foreign key (user_name) references spring_user;
```

## 陥ったトラブル

**トラブル1: KotlinNullPointerException**
[jpa-schema-gradle-plugin](https://github.com/divinespear/jpa-schema-gradle-plugin)のHow to Useを見ると、
persistence.xmlを使用しない場合の最低限の設定値は、
`vendor = 'hibernate'`
`packageToScan = ['com.example.domain']`
だけのように見えるがこれだと、generateSchemaを実行したときにKotlinNullPointerExceptionが発生する。
⇒解決方法: databaseProductNameを設定する必要がある。

**トラブル2: gradleタスクにgenerateSchemaは登録されるが、実行してもcreate.sqlが作成されない**
イージーミスでした。`scriptAction = 'drop-and-create'`が抜けているだけでした。

## まとめ
DDLを自動生成することで、プロジェクト参画メンバーの環境構築を円滑に行うことができるため、このプラグインの導入はおすすめです。
まぁ最近ではflywayとか使ってDDLの自動生成や移行の簡略化とかも簡単にできますが、一度自分でプラグインを作ってみたり、追うことで新しい技術を取り込むときのハードルを下げることができると思っています。

ここまで読んでいただきありがとうございました。:bow: