docker-compose 下で Java + Spring Boot + PostgreSQL (Spring Data JDBC編)
TL;DR
- 下記記事に書いてあるようなことをします
- Spring Bootキャンプ:Spring Boot + Spring Data JDBC編 | Qiita
- Spring Data JDBCの使い方メモ | Qiita
- Spring Data JPA編 はこちら
- docker-compose を用意するあたりは同じです
- docker-compose 下で Java + Spring Boot + PostgreSQL (Spring Data JPA編) | 北山淳也 | zenn
docker-compose.yml を用意する
cd your_project
mkdir server
touch docker-compose.yml
version: '3.6'
services:
app:
image: openjdk:15
container_name: app
ports:
- 8080:8080
tty: true
volumes:
- ./server:/srv:cached
working_dir: /srv
depends_on:
- db
adminer:
image: adminer:4.7.8
container_name: adminer
ports:
- "9000:8080"
depends_on:
- db
db:
image: postgres:13.1
container_name: db
environment:
POSTGRES_USER: "root"
POSTGRES_PASSWORD: "root"
POSTGRES_DB: "dev"
ports:
- "5432:5432"
volumes:
- dbvol:/var/lib/postgresql/data
- ./forDocker/db/initdb:/docker-entrypoint-initdb.d
volumes:
dbvol:
- OpenJDK | DockerHub
- JDK Project Releases | OpenJDK
- 指定できるバージョン情報はここ
- http://openjdk.java.net/projects/jdk/
- postgres | DockerHub
-
/docker-entrypoint-initdb.d
にマウントしている./forDocker/db/initdb
については後述します -
/docker-entrypoint-initdb.d
については Initialization scripts の頁を参照してください - https://hub.docker.com/_/postgres
-
- Adminer | DockerHub
- PostgreSQL にも対応したPHP製の GUI SQL クライアント
- https://hub.docker.com/_/adminer
DB初期投入クエリの準備
postgres の Docker イメージは前述の DockerHub ページに書いてあるように、
/docker-entrypoint-initdb.d
に用意したファイルを
サービス開始前に実行してくれます。(MySQL の Docker イメージにも同様の機能がありますね)
このクエリは /var/lib/postgresql/data
を生成するときに実行されるため、
再度実行させたい時は
今回の docker-compose.yml
であれば docker volume を割り当てているので
dbvol
を docker volume rm ...
で削除してやれば再度実行させることができます。
ではその前提で、DB初期投入クエリの準備をしていきましょう。
mkdir -p forDocker/db/initdb
touch forDocker/db/initdb/1_create_users.sql
touch forDocker/db/initdb/2_insert_users.sql
CREATE TABLE users (
id SERIAL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO users (name) values ('test');
ファイル名順に実行してくれるのでファイル名にシーケンスを振っています。
Gradleプロジェクト作成
今回も Spring Initializr というサイトで作ってしまいます。
- Spring Initializr
PostgreSQL Driver, Spring Data JDBC などを Dependencies に追加するのを忘れないようにしてください。今回はこの Spring Data JDBC を使用します。
Spring JDBC と Spring Data JDBC について
- JDBC
- Java で DB アクセスを行うための API, ドライバ
- DB製品間の差異はJDBCドライバが吸収する
- 最も低レイヤーな部分
- .NET Framework でいうところの ADO.NET
- Spring JDBC
- JDBC の薄いラッパー
- 似たようなものに Apache Commons DBUtils, sql2o がある
- Data Access | Spring
- Spring Data JDBC
- Spring Data ファミリの一部としての JDBC ラッパー
- Spring Data 共通の Entity, Repository が使える
- 簡単であることを目指したシンプルで限定された ORM
- Spring Data JDBC とは? | Spring リファレンスドキュメント
- java - spring-jdbc vs spring-data-jdbc and what are they supporting | Stack Overflow
その他 Java での DB アクセスパッケージについては以下が詳しいです
- Java ORマッパー選定のポイント | SlideShare
入力が終わったら GENERATE してダウンロードします。
ダウンロードしたら展開したものを
your_project/server 配下に配置します。
server/src/main/java/com/example/api/ApiApplication.java はダウンロードしてきたままでOK.
package com.example.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
server/src/main/resources/application.properties を以下のように編集します。
spring.datasource.url=jdbc:postgresql://db:5432/dev
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
-
spring.datasource.url=jdbc:postgresql://db:5432/dev
- Docker としてアクセスする場合は
jdbc:postgresql://{DBコンテナ名}:{ポート}/{DB名}
とするようです
- Docker としてアクセスする場合は
- その他のオプションについては以下を参照
- Spring Boot アプリケーションプロパティ設定一覧 - Spring リファレンス
Spring Data JDBC で必要な Entity と Repository を作成
Spring Data JDBC では色んな方法でデータベースアクセスが定義できるのですが、
今回は最も簡易で Spring Data JPA との違いがわかりやすい
Entity と Repository を利用してアクセスする方法を使いましょう。
# server/src/main/java/com/example/app までは Spring Initializr からDLしたものを展開した時点で存在するはずです
mkdir server/src/main/java/com/example/app/entity
touch server/src/main/java/com/example/app/entity/User.java
mkdir server/src/main/java/com/example/app/repository
touch server/src/main/java/com/example/app/repository/UserRepository.java
package com.example.app.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("users")
public class User {
@Id
private Long id;
private String name;
}
今回は Entity の作成に Lombok
を利用しています。
Lombok
はアノテーションを付けるだけで、 getter, setter, toString, equals などの「何度も繰り返し書くコード」をコンパイル時に自動生成してくれるようになる便利なパッケージです。
詳しくは以下の解説がわかりやすいです。
- Lombok 使い方メモ | Qiita
- Lombok @Data | 覚えたら書く
- 劇的にコード量を減らせるLombok | Java好き
- Java アノテーションとは? | Qiita
また、今回は Entity と実際のテーブル名とのマッピングに @Table
を使っていますが
これは Spring Data JDBC で定義されたアノテーションです。
- java How to map entity to table in Spring Data JDBC? | Stack Overflow
- spring-data-jdbc/Table.java at master | spring-projects/spring-data-jdbc | GitHub
package com.example.app.repository;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.example.app.entity.User;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
@Query(" SELECT"
+ " COUNT(*)"
+ " FROM users"
+ " WHERE"
+ " id >= 5 ")
long countOverFive();
}
DI を利用して User にアクセスする Controller を作成
Spring の強力なDIを使って Controller を用意します。
mkdir server/src/main/java/com/example/app/controller
touch server/src/main/java/com/example/app/controller/UserController.java
package com.example.app.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.app.entity.User;
import com.example.app.repository.UserRepository;
@RestController
public class UserController {
private final UserRepository repository;
@Autowired
public UserController(UserRepository repository) {
this.repository = repository;
}
@GetMapping("/")
public Iterable<User> user() {
// CrudRepository の メソッドが使える
return repository.findAll();
}
@PostMapping("/")
public User create(@RequestBody User user) {
// CrudRepository の メソッドが使える
var savedUser = repository.save(user);
return savedUser;
}
@RequestMapping("/count")
public String count() {
// CrudRepository の メソッドが使える
return String.valueOf(repository.count());
}
@RequestMapping("/countoverfive")
public String countOverFive() {
// 新しく定義したクエリも呼び出せる
return String.valueOf(repository.countOverFive());
}
}
@Autowired をつけることで Spring の DI 機能により new して突っ込んでくれます。
以下などが分かりやすかったです。
- 【Spring】@Autowired と @Component を使用した DI の基本 | 山崎屋の技術メモ
@RestController と @RequestMapping("/") は以下記事でも扱いましたね。
本来であれば Repository を扱う Domain Model(Service) を作った方がいいんですが今回は割愛しました。
- docker-compose 下で Java + Spring Boot + 簡単なWeb API を作ってみる | 北山淳也 | zenn
- @RequestMapping などのより詳しい解説はこちら
- Spring MVC コントローラの引数 | Qiita
- @PostMapping and @RequestBody Example in Spring MVC | Apps Developer Blog
Docker コンテナを起動してGradleビルド, アプリケーション起動, リクエストを投げてみる
ビルドする前に server/build.gradle を編集してバージョンを抜いてみましょう。
開発中は特に不要と思いますので。
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '15'
ここを、こう
group = 'com.example'
sourceCompatibility = '15'
ではコンテナを起動してビルド、Spring アプリケーションを起動します。
docker-compose up -d
# DB が立ち上がって初期化されるまでちょっとかかるのでちょっと待つ
docker-compose exec app bash
bash-4.4# sh gradlew build
...
BUILD SUCCESSFUL in 1m 41s
5 actionable tasks: 5 executed
# できてるのを確認
bash-4.4# ls build/libs/
app.jar
bash-4.4# java -jar build/libs/app.jar
起動したらリクエストを投げてみましょう。
$ curl http://localhost:8080 -X GET
[{"id":1,"name":"test"}
$ curl http://localhost:8080 -X POST -H "Content-Type:application/json" -d '{ "name":"foo" }'
{"id":2,"name":"foo"}
$ curl http://localhost:8080 -X POST -H "Content-Type:application/json" -d '{ "name":"bar" }'
{"id":3,"name":"bar"}
$ curl http://localhost:8080 -X POST -H "Content-Type:application/json" -d '{ "name":"baz" }'
{"id":4,"name":"baz"}
$ curl http://localhost:8080 -X POST -H "Content-Type:application/json" -d '{ "name":"hoge" }'
{"id":5,"name":"hoge"}
$ curl http://localhost:8080/count -X GET
5
$ curl http://localhost:8080/countoverfive -X GET
1
OK ですね!
アプリケーションの終了は例によって Ctrl+c です。
コンテナから出るのは exit, コンテナの停止は docker-compose down で。
Spring Data REST を使うよりはもちろん記述量は増えるのですが、
より柔軟な書き方ができるのが伝わったのではないでしょうか。
Spring Data JPA などの ORM は複雑なリレーションシップがある場合に
N+1 問題などとの戦いが発生するので、
SQLを直接扱えるのは個人的には好みな感じです。
今回のリポジトリはこちらです。
参考
- Spring Data JDBC | Spring リファレンスドキュメント
- Spring Bootキャンプ:Spring Boot + Spring Data JDBC編 | Qiita
- Spring Data JDBCの使い方メモ | Qiita
- 【今更ながらSpring BootでWEB開発 #5】データベースの接続 | bitBlog
- Repository ではなく Dao を用いた Spring Data JDBC についての記述
- https://b1tblog.com/2020/02/03/spring-boot-5/
- Spring BootでのDBアクセス方法(JDBC、JPA、MyBatis)について | Enjoy*Study
- Introduction to Spring Data JDBC | Baeldung
- Azure Database for PostgreSQL で Spring Data JDBC を使用する | Microsoft Docs
- Spring Data JDBC 1.0.0.BUILD-SNAPSHOT(-> 1.0.0.RELEASE)を試してみた | Qiita
- spring-boot-starter-data-jdbc(2.1.0.BUILD-SNAPSHOT)を試す | Qiita
Discussion