🐥

docker-compose 下で Java + Spring Boot + PostgreSQL (Spring Data JPA編)

2020/12/19に公開

そろそろタイトルの文字数がキツくなってきました……

TL;DR

docker-compose.yml を用意する

cd your_project
mkdir server
touch docker-compose.yml
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:

DB初期投入クエリの準備

postgres の Docker イメージは前述の DockerHub ページに書いてあるように、
/docker-entrypoint-initdb.d に用意したファイルを
サービス開始前に実行してくれます。(MySQL の Docker イメージにも同様の機能がありますね)

このクエリは /var/lib/postgresql/data を生成するときに実行されるため、
再度実行させたい時は
今回の docker-compose.yml であれば docker volume を割り当てているので
dbvoldocker volume rm ... で削除してやれば再度実行させることができます。

ではその前提で、DB初期投入クエリの準備をしていきましょう。

mkdir -p forDocker/db/initdb
touch forDocker/db/initdb/1_create_users.sql
touch forDocker/db/initdb/2_insert_users.sql
touch forDocker/db/initdb/3_create_role_appuser.sql
forDocker/db/initdb/1_create_users.sql
CREATE TABLE users (
  id SERIAL,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);
forDocker/db/initdb/2_insert_users.sql
INSERT INTO users (
  name
) values ( 
  'test'
);
forDocker/db/initdb/3_create_role_appuser.sql
CREATE ROLE appuser WITH LOGIN PASSWORD 'apppass';
GRANT SELECT,UPDATE,INSERT,DELETE ON ALL TABLES IN SCHEMA public TO appuser;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO appuser;

ファイル名順に実行してくれるのでファイル名にシーケンスを振っています。
3_create_role_appuser.sql でJavaアプリケーションから利用する用のユーザを作成しています。
気持ちとしてはテーブル作成や初期投入データINSERTの前にユーザを作成したいところですが、
GRANT...ON ALL TABLES IN SCHEMA public TO... では
既に作成済みのテーブルにしか権限が付与されないようだったので
このようなクエリ順になっています。
DBに対して権限を付与してやれば最初でも大丈夫ですが、今回はちょっとうまくいかなかったので割愛します。

Gradleプロジェクト作成

今回も Spring Initializr というサイトで作ってしまいます。

PostgreSQL Driver と Spring Data JPA を Dependencies に追加するのを忘れないようにしてください。
Spring Boot で扱える ORM にはいくつか種類があるんですが、
今回は参考にした記事でも使っている Spring Data JPA を使用します。

閑話休題。
入力が終わったら GENERATE してダウンロードします。
ダウンロードしたら展開したものを
your_project/server 配下に配置します。

server/src/main/java/com/example/api/ApiApplication.java はダウンロードしてきたままでOK.

server/src/main/java/com/example/api/ApiApplication.java
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 を以下のように編集します。

server/src/main/resources/application.properties
spring.datasource.url=jdbc:postgresql://db:5432/dev
spring.datasource.username=appuser
spring.datasource.password=apppass
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名} とするようです
    • DB名 は docker-compose.yml で dev としましたね
  • spring.datasource.username=appuser
  • spring.datasource.password=apppass
    • forDocker/db/initdb/3_create_role_appuser.sql で用意したユーザです
  • その他のオプションについては以下を参照

Spring Data JPA で必要な Entity と Repository を作成

Spring Data JPA は ORM なので
Entity(テーブルのマッピング先) と Repository(DBアクセスリポジトリ) を作成するだけで
DB Access が可能です。

# 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
server/src/main/java/com/example/app/entity/User.java
package com.example.app.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "users")
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;

  protected User() {}

  public User(String name) {
    this.name = name;
  }

  public Long getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return String.format("{id:%d,name:%s}", id, name);
  }
}

@GeneratedValue は DBの設定と連携して IDの自動採番をする設定です。
詳しくは以下が分かりやすかったです。
forDocker/db/initdb/1_create_users.sql にて
users.id は SERIAL で定義しましたね。

server/src/main/java/com/example/app/repository/UserRepository.java
package com.example.app.repository;

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> {
}

UserRepository には今回特に新しい定義は追加しないので
CrudRepository のインターフェースをそのまま利用します。

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
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.app.repository.UserRepository;

@RestController
public class UserController {

  private final UserRepository repository;

  @Autowired
  public UserController(UserRepository repository) {
    this.repository = repository;
  }

  @RequestMapping("/")
  public String user() {
    return String.valueOf(repository.findAll());
  }
}

@Autowired をつけることで Spring の DI 機能により new して突っ込んでくれます。
以下などが分かりやすかったです。

@RestController@RequestMapping("/") は以下記事でも扱いましたね。
今回、Response は String にしています。
本来であれば Repository を扱う Domain Model(Service) を作った方がいいんですが今回は割愛しました。

Docker コンテナを起動してGradleビルド, アプリケーション起動, リクエストを投げてみる

docker-compose up -d
# DB が立ち上がって初期化されるまでちょっとかかるのでちょっと待つ
docker-compose exec app bash
bash-4.4# sh gradlew build
...
BUILD SUCCESSFUL in 8m 41s
5 actionable tasks: 5 executed
# できてるのを確認
bash-4.4# ls build/libs/
app-0.0.1-SNAPSHOT.jar
bash-4.4# java -jar build/libs/app-0.0.1-SNAPSHOT.jar

起動したらリクエストを投げてみましょう。

$ curl http://localhost:8080 -X GET
[{id:1,name:test}]

OK ですね!
アプリケーションの終了は例によって Ctrl+c です。
コンテナから出るのは exit, コンテナの停止は docker-compose down で。

Adminer からも PostgreSQL に接続してみる。

docker-compose.yml で書いたように
http:localhost:9000 で Adminer にアクセスできます。

こんな感じでログインできるので、
ログインしたら左側の「SQLコマンド」でSQLコマンドが発行できるので
こんな感じでクエリ結果をみることが出来ます。便利!

今回のリポジトリはこちらです。
https://github.com/JUNKI555/java_spring_boot_practice02

参考

Discussion