概要・背景
SpringBootのJavaアプリにおいて、application.propertiesにspring.jpa.hibernate.ddl-auto=create-drop
とかDDLに関する設定値を定義すればテーブルを自動生成してくれるけど、
「毎回dropされてデータが初期化されてしまう」とか「移行用とかなんだかんだでDDL必要だよね」っていうシーンでは融通が利かないので、DDLを自動生成するgradleタスクを作成した。
良さげなプラグインjpa-schema-gradle-pluginが公開されていたのでこれを利用する。
このプラグインは指定パッケージ配下(packageToScanの設定値)の中でEntityクラスのものを読み込んでDDLを作成してくれる。
前提条件
Spring Boot
gradle
(build.gradleの使い方とかは理解しているものとする。)
準備
1. プラグイン呼び出しのアプリ設定
jpa-schema-gradle-pluginのHow to Useに従い、必要な設定値を定義していく。
今回自分はDBはOracle、OR mapperはHibernateなので、最終的な最終的な設定値は以下のようになった。
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を用意した。
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;
}
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が作成される(デフォルトの出力先)。
出来上がったcreate.sqlがこちら。
Customer.javaで定義した通り、SEQUENCEもきちんと反映して作成されている。
カラムの物理名に"_"をいれるかどうかなどは、build.gradleで設定した値"SpringPhysicalNamingStrategy"で変更したりできるので、細かい仕様は、jpa-schema-gradle-pluginのHow to Useを読めばいいと思います。
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の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の自動生成や移行の簡略化とかも簡単にできますが、一度自分でプラグインを作ってみたり、追うことで新しい技術を取り込むときのハードルを下げることができると思っています。
ここまで読んでいただきありがとうございました。