Spring Boot × PostgreSQL × Docker(Compose V2)でお手軽環境構築

## はじめに
こんにちは！ @Keichan_15 です！

以前 [Spring BootとPostgreSQLのローカル環境構築に関するハンズオン](https://qiita.com/Keichan_15/items/b732bea7c9868c1e9f6c) を執筆した際に、ちょうどDockerを学んでいたこともあってDockerでも構築できるのかな～って興味本位で調べてみたら、どうやらありました！:clap:

今回はそれらを参考にしつつ、Dockerを使用してローカル環境を汚さずに比較的シンプルな構成で環境構築を行っていきたいと思います！

よろしくお願い致します！

## 環境
- Windows11(Windows10)
- Docker Desktop


## 準備
任意のディレクトリにフォルダを作成します。
自分は今回 `spring_boot` という名前のフォルダを作成しました。

```:Windows PowerShell
> mkdir spring_boot
> cd spring_boot
```
この時点で`Visual Studio Code`を起動し、`Visual Studio Code`内のターミナル上で操作していきます。

```:Windows PowerShell
> code .
```

上記コマンドを入力すると、`Visual Studio Code`が開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/9083a5f6-a60c-7a90-d68e-554b8bbe3342.png)

ここまで出来れば準備は完了です！

## compose.yamlの作成・編集
続いて`compose.yaml`を作成・編集します。今回はDocker Compose V2に倣って作成していきます。

```yaml:compose.yaml
services:
  app:
    image: openjdk:17
    container_name: app
    ports:
      - 8080:8080
    tty: true
    volumes:
      - ./server:/srv:cached
    working_dir: /srv
    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:
```

## PostgreSQL DB準備
DBに初期データを登録します。こちらは以下記事のデータをお借りさせて頂きました。

https://zenn.dev/junki555/articles/de2c9844a1d101

まずは初期データ登録用のディレクトリとSQLファイルを作成します。

```terminal:terminal
$ 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
```

```sql:forDocker/db/initdb/1_create_users.sql
CREATE TABLE users (
  id SERIAL,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);
```

```sql:forDocker/db/initdb/2_insert_users.sql
INSERT INTO users (
  name
) values ( 
  'test'
);
```

```sql: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;
```

ファイル名の順番(1 → 2 → 3)に処理を実行します。

今回はテストデータなので簡易的ですが、実際にWebアプリケーションを作成していく場合はテーブル定義をよく検討した上で作成することになると思います。


## プロジェクト作成
次に`Gradle`プロジェクトを作成します。今回は `spring initializr` を使用します。

https://start.spring.io/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/b34fbc4d-d1eb-6631-5da4-a73c1d75f562.png)

左側の `Aritfact` を `demo` -> `app` に変更しました。
連動して `Name` と `Package name` も変更されます。便利ですね。

また、右側の `Dependencies` は計**7つ**追加しています。

- **Spring Web**
- **Spring Boot DevTools**
- **Lombok**
- **Docker Compose Support**
- **Thymeleaf**
- **Spring Data JPA**
- **PostgreSQL Driver**

全て入力・追加が完了したら、画面下部の `GENERATE` ボタンを押下します。
押下後すぐにダウンロードが開始すると思います。

ダウンロードしたzipファイルを解凍し、appフォルダの中身全てを `server` フォルダ内に移動させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/54f167ea-319b-0285-3f60-a2cd0ba47414.png)


次に `application.properties` を編集します。

```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
```

## Entity & Repository 作成
DBへアクセスする場合、Spring Data JPAによってEntityとRepositoryを作成することで接続が可能です。
こちらは以前自身の記事でも紹介しましたね。

```terminal
$ 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
```

まず`Entity`を作成します。

```java:server/src/main/java/com/example/app/entity/User.java
package com.example.app.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.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);
  }
}
```

1点注意が必要な部分として `import` が挙げられます。

SQLの章でご紹介した以下の記事内では `javax` から `import` を行っています。

https://zenn.dev/junki555/articles/de2c9844a1d101

> ```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);
>   }
> }
> ```

上記のままでは後のビルド時にエラーが出ます。

こちらはSpring Bootが3系に移行するにあたり、**名前空間の変更** が必要になったことが理由です。

公式ブログでも以下のように紹介されています。

https://spring.io/blog/2021/09/02/a-java-17-and-jakarta-ee-9-baseline-for-spring-framework-6

> __Even more importantly, there might be some changes required in your application source code: e.g. the javax to jakarta namespace change in Jakarta EE 9 wherever you're touching the Servlet API, JPA, Bean Validation, etc._

> さらに重要なこととして、アプリケーションのソースコードに変更が必要になるかもしれません。例えば、サーブレットAPI、JPA、Bean Validationなどを触っているところでは、Jakarta EE 9でjavaxからjakartaへの名前空間の変更があります。

```diff_java
- import javax.persistence.Entity;
+ import jakarta.persistence.Entity;
```

上記のように `javax` の部分を `jakarta` に全て変更しておきましょう。

続いて`Repository`を作成します。

```java:server/src/main/java/com/example/app/entity/User.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> {
}
```

## Controller作成
続いて`Controller`を作成します。こちらも参考記事からお借りしました。

```terminal
$ mkdir server/src/main/java/com/example/app/controller
$ touch server/src/main/java/com/example/app/controller/UserController.java
```

```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());
  }
}
```

## コンテナ起動 & 動作確認
一通り準備が完了したので、コンテナを起動して動作確認してみます。

```docker:terminal
# コンテナをバックグラウンドで起動
$ docker compose up -d

# コンテナに入る
$ docker compose exec app bash

# buildする
bash-4.4# sh gradlew build
```

ここまで入力すると、おそらく以下のエラーが表示されるかと思います。

```docker:terminal
bash-4.4# sh gradlew build

xargs is not available
```

解決方法が以下の記事中に紹介されています。

https://qiita.com/k1morikawa@github/items/d0db380a1ea860cb2e12

bashに `microdnf install findutils` と入力することで解決するようです。

> ```docker:terminal
> bash-4.4# microdnf install findutils
> ```

実行すると、以下のように `Complete.`が表示されていればOKです。
```docker:terminal
bash-4.4# microdnf install findutils
Downloading metadata...
Downloading metadata...
Package                                                                                                                                       Repository                                              Size
Installing:
 findutils-1:4.6.0-20.el8.x86_64                                                                                                              ol8_baseos_latest                                   540.6 kB
Transaction Summary:
 Installing:        1 packages
 Reinstalling:      0 packages
 Upgrading:         0 packages
 Obsoleting:        0 packages
 Removing:          0 packages
 Downgrading:       0 packages
Downloading packages...
Running transaction test...
Installing: findutils;1:4.6.0-20.el8;x86_64;ol8_baseos_latest
Complete.
```

再度コマンドを入力していきます。
`build` からですね。

```docker:terminal
bash-4.4# sh gradlew build
BUILD SUCCESSFUL in 2m 7s
7 actionable tasks: 7 executed

# buildしたjarファイルが存在するか確認
bash-4.4# ls build/libs/
app-0.0.1-SNAPSHOT-plain.jar  app-0.0.1-SNAPSHOT.jar

# jarファイルを実行
bash-4.4# java -jar build/libs/app-0.0.1-SNAPSHOT.jar
```

ここまで実行が完了したら、http://localhost:8080/ にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2798073/cd99a048-7b51-8b15-5730-2e7ff75c00da.png)

このような画面が表示されていればOKです！お疲れ様でした！

## 参考

https://zenn.dev/junki555/articles/de2c9844a1d101

https://hub.docker.com/_/postgres

https://eh-career.com/engineerhub/entry/2023/06/29/093000

## おわりに
いかがでしたでしょうか。
今回はDockerを使用してSpring Boot × PostgreSQLの環境構築を行いました。

ローカル環境を汚さない & Visual Studio Codeでコードが書けるというメリット尽くしなので、eclipseもう飽きた！って人はおススメですね。

それではまた！:wave: