概要
このエントリでは、Spring Bootをベースとして、データベースにH2 Database Engineを指定し、Spring Data JPAでアクセスするときの例をまとめたものです。
題材として、郵便番号を検索するAPIをイメージしました。例えば、地名に「猫」がつくものを調べると、下図のような感じです。
想定読者
- エントリのタイトル通りのことをしたい方が参考とする目的に
エントリの動機
- 何となく各サイトからコピペしてひな型を作ってから始めることが複数回あるので、自分用のボイラープレートを作っておくため
- もしかするとどなたかの参考になるかもしれないので
このエントリでできるようになること
エントリ冒頭の図のように、郵便番号と住所を検索できます。
郵便番号と住所を格納したテーブルを対象として、以下2つの方法で検索するためのAPIを作ります。
- 郵便番号を指定して、対応する行の一覧を取得する
- 地名の一部を指定して、対応する行の一覧を取得する
- 郵便局のページで郵便番号がダウンロードできるので、データベースにロードして、RESTアクセスできるようにします。
使用環境
- Windows10 Pro 64bit
- Adopt Open JDK 1.8.0
Javaのコード書くまでの手順
Spring Initializr
ざくっとこんな感じです。
初期状態のGitHubタグはこちら。
H2のコンソールが出せるようにする
H2データベースには、有効化するとすぐに使えるWEBコンソールの機能が付いてきます。(下図参照)
有効化するには、application.yaml、および、開発時にだけ有効化するという意味でapplication.yamlに、それぞれ、以下のように指定します。
application.yamlは以下の通り。特にしていしないとき、@Entityを元にDDLを発行してくれる機能がSpring Boot JPAにはあるのですが、DDLは自分で指定したいため、ここでは止めています。
H2の接続文字列で「;database_to_upper=false」を指定してあるのは、H2のデフォルトではDDL発行時にテーブル名が大文字に揃えられるのですが、この機能を無効化してあるものです。JPA+Hibernate側が小文字をデフォルトとして期待しているため、そちらに揃えました。
sqlScriptEncodingは、起動時に読み込むようにしてあるデータの文字化けを防ぐものです。(例えば、Windows Platformだと、プラットフォームのエンコーディングをSJIS(MS932)として動作しようとするため)
# properties: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html
spring:
datasource:
platform: h2
driver-class-name: org.h2.Driver
url: jdbc:h2:./demodb;database_to_upper=false
username: username
password: password
sqlScriptEncoding: UTF-8
h2:
console:
enabled: false
jpa:
hibernate:
ddl-auto: none
application-dev.yamlは下記の通り。SQLがログに出ている方が動きが見えやすいので、devプロファイルでは、jpa.show-logをtrueに指定しています。
# properties: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html
spring:
h2:
console:
enabled: true
path: /h2-consolea
settings:
web-allow-others: true
jpa:
show-sql: true # for debugging-purpose only. Because it shows everything, there's no ways to filter.
properties:
hibernate:
format_sql: true
この状態で、以下のように起動するにSpring のProfileを指定して起動することで、WEBコンソールが有効化されます。
Power Shellの場合:(Spring Boot2以降で、下記のようにbootRunに対してパラメータを渡せます)
.\gradlew bootRun --args='--spring.profiles.active=dev'
設定ファイルで指定してある、http://localhost:8080/h2-console に対して、設定ファイルで指定してあるusername, passwordの組でログインできます(下図)。
起動時にデータをロードしてみる
Spring Bootでは、起動時に、DBのスキーマのSQLを読み込む機能、DBのデータを読み込む機能があります。
それぞれ、src/main/resources以下にファイルを配置することで実現できます。
データソースを複数用いたり、切り替えたりする場合などには、各DB固有の
構文が異なる場合がありますが、その場合には、書くプラットフォーム用にファイルを置くことで対応できます。(このあたりに説明があります)
このサンプルでは、以下のようなファイルを用意しました。例として、郵便番号と住所を格納するスキーマを使っています。
src/data/resources/schema-h2.sql でテーブルを作っています。毎回走る処理なので、IF NOT EXISTSをつけています。IDは、H2の機能を使ってつけることにしました。
CREATE TABLE IF NOT EXISTS
postal_codes (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
code7 VARCHAR(7) NOT NULL,
address1 VARCHAR(12) NOT NULL,
address2 VARCHAR(60),
address3 VARCHAR(60)
);
src/data/resources/data-h2.sql で、初期データを2件投入しています。主に開発時の動作確認用です。空だとさびしいので。
こちらも、起動時に毎回走る処理なので、重複してINSERTしないようなSQLにしてあります。
INSERT INTO postal_codes (code7, address1, address2, address3) select '0640941', '北海道', '札幌市中央区', '旭ケ丘' where not exists (select * from postal_codes where code7 = '0640941');
INSERT INTO postal_codes (code7, address1, address2, address3) select '0600041', '北海道', '札幌市中央区', '大通東' where not exists (select * from postal_codes where code7 = '0600041');
(おまけ)H2のCSVREAD機能を使ってCSVをテスト用に読み込む
H2には、CSVからデータを読み込み、selectすることができます。
これを使って、郵便局の郵便番号情報のダウンロードサイトから、全国分のデータが収録されているファイル「KEN_ALL.CSV」を使用します。
前述のH2のWEBコンソールにて、下記を実行します。CSVデータにはカラム名がなかったため、CSVREADのオプションとしてカラム名を指定し、selectしています。その結果をpostal_codesテーブルに格納しています。
insert into postal_codes (code7, address1, address2, address3) select "col3", "col7", "col8", "col9" from csvread('~/KEN_ALL.CSV', 'col1,col2,col3,col4,col5,col6,col7,col8,col9,col10,col11,col2,col3,col4,col15', 'charset=SJIS')
この時点で、クエリするためのデータが用意できました。
Javaのコード
たった3つです。
Main
いつもの、SpringBootApplicationです。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Entity
DBからデータをロードするためのクラスを用意します。Spring Data JPAには、@Tableを指定しなくてもクラス名から自動で対応するテーブルを作ってくれる機能もありますが、ここでは明示的に指定しています。
Setter/Getterは、lombokを使って自動生成しています。@javax.persistense.Idを付与してある「id」は、H2で自動採番したものと対応しています。
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "`POSTAL_CODES`")
@Data
public class PostalCodeInfo {
@Id
private long id;
private String code7;
private String address1;
private String address2;
private String address3;
}
Repository
リポジトリ層です。「PagingAndSortingRepository」を継承することにより、Spring Data JPA, RESTあたりが、DBアクセスや、外部からのRESTアクセスに必要な実装を提供してくれます。
@RepositoryRestResource アノテーション内のパラメータも、特に指定しなくてもフレームワーク側で付与してくれますが、この例では明示的に指定しています。
@RepositoryRestResource(collectionResourceRel = "postalcode", path = "postalcodes")
public interface PostalCodeInfoRepository extends PagingAndSortingRepository<PostalCodeInfo, Long> {
List<PostalCodeInfo> findByCode7(@Param("code") String code);
@Query("SELECT p FROM PostalCodeInfo p "
+ "WHERE address1 LIKE CONCAT('%',:name,'%')"
+ " OR address2 LIKE CONCAT('%',:name,'%')"
+ " OR address3 LIKE CONCAT('%',:name,'%')"
+ " ORDER BY p.code7")
List<PostalCodeInfo> anyFieldLike(@Param("name") String name);
}
メソッドが2つありますが、1つめの方は、Spring Data JPAの命名ルールに沿ってメソッド名をつけています。命名ルールに従ったメソッドについては、対応するSQL文とパラメータをフレームワーク側が実装してくれます。なので、郵便番号7桁から住所を検索するために必要なコードは、この1行だけです。
List<PostalCodeInfo> findByCode7(@Param("code") String code);
もうひとつのメソッドでは、自分でクエリを指定しています。住所に関係するカラムは3つありますが、そのどれかの中で、LIKEでマッチするものを探すものになります。
@Query("SELECT p FROM PostalCodeInfo p "
+ "WHERE address1 LIKE CONCAT('%',:name,'%')"
+ " OR address2 LIKE CONCAT('%',:name,'%')"
+ " OR address3 LIKE CONCAT('%',:name,'%')"
+ " ORDER BY p.code7")
List<PostalCodeInfo> anyFieldLike(@Param("name") String name);
この時点のコードをGitHubにタグ0.1.0として置いておきます。
おわりに
このエントリでは、SpringBootとH2 DBとSpring Data RESTを使用する例を扱いました。
フレームワーク側で用意された範囲でやりたいことが実現できる範囲であれば、書かなくてはいけないコードはかなり少なく済むことが確認できました。
補足
コード3つの配置パッケージをどうしようかと一瞬考えましたが、この例ではシンプルにべたっと同一フォルダに置くにとどめました。
流儀によって、好きな位置に配置すればよいと思います。