Docker-composeでSpringBoot×PostgreSQL環境構築

## 事前知識
https://qiita.com/ryome/items/240f36923f5cb989da27

https://qiita.com/ryome/items/4b6b934b1b2021acfa26

https://qiita.com/ryome/items/6e59dcbe4735d45d58a3

https://qiita.com/ryome/items/3d4de62b560aac31bede

https://qiita.com/ryome/items/56a3263f347a08bd860f

https://qiita.com/ryome/items/35df47a01a7fe3ac70ee

## 環境

| 環境               | バージョン |
| ------------------ | ---------- |
| Docker             | 20.10.17   |
| Docker-Compose     | 1.29.2     |
| Java               | 11         |
| Spring Boot        | 2.7.3      |
| Maven              | 3.6.3      |
| PostgreSQL         | 14         |
| Swagger（openapi） | 3.0        |

## フォルダ構成
```
docker-spring-psql
├─spring
│  ├──src
│  │   └─...省略
│  ├─pom.xml
│  └─Dockerfile
├─init
│  └─sql
│      └─create_table.sql
├─swagger
│      └─swagger.v1.yml
├─others
│  ├──テーブル定義書
│  │   ├─tables
│  │   │  └─...省略
│  │   ├─index.html
│  │   └─a5m2.css
│  └─テーブル定義書.xlsx
└─docker-compose.yml
```

## 解説

### create_table.sql

<details><div>

```sql:create_table.sql
/* テーブルが存在する場合は削除 */
DROP TABLE IF EXISTS common_mst;

DROP TABLE IF EXISTS user_mst;

/* ユーザマスタ作成 */
CREATE TABLE user_mst(
        user_id VARCHAR(12) NOT NULL,
        password VARCHAR NOT NULL,
        user_name VARCHAR(100) NOT NULL,
        secret_id VARCHAR(7) NOT NULL,
        secret_answer VARCHAR(200) NOT NULL,
        error_password SMALLINT,
        lock BOOLEAN,
        authority SMALLINT NOT NULL DEFAULT 0,
        update_datetime DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        version SMALLINT NOT NULL DEFAULT 0,
        PRIMARY KEY(user_id)
);

/* 汎用マスタ作成 */
CREATE TABLE common_mst(
        common_id VARCHAR(7) NOT NULL,
        common_group VARCHAR(100) NOT NULL,
        common_name VARCHAR(200) NOT NULL,
        common_value1 VARCHAR(200),
        common_value2 VARCHAR(200),
        update_datetime DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        version SMALLINT NOT NULL DEFAULT 0,
        PRIMARY KEY(common_id)
);

/* ユーザマスタコメント */
COMMENT ON TABLE user_mst IS 'ユーザマスタ';

COMMENT ON COLUMN user_mst.user_id IS 'ユーザID';

COMMENT ON COLUMN user_mst.password IS 'パスワード';

COMMENT ON COLUMN user_mst.user_name IS 'ユーザ名';

COMMENT ON COLUMN user_mst.secret_id IS '秘密の質問ID';

COMMENT ON COLUMN user_mst.secret_answer IS '秘密の答え';

COMMENT ON COLUMN user_mst.error_password IS 'パスワード間違え回数';

COMMENT ON COLUMN user_mst.lock IS 'アカウントロック';

COMMENT ON COLUMN user_mst.authority IS '権限';

COMMENT ON COLUMN user_mst.update_datetime IS '更新日時';

COMMENT ON COLUMN user_mst.version IS 'バージョン';

/* 汎用マスタコメント */
COMMENT ON TABLE common_mst IS '汎用マスタ';

COMMENT ON COLUMN common_mst.common_id IS '汎用マスタID';

COMMENT ON COLUMN common_mst.common_group IS '汎用マスタグループ';

COMMENT ON COLUMN common_mst.common_name IS '汎用マスタ名';

COMMENT ON COLUMN common_mst.common_value1 IS '汎用マスタ値';

COMMENT ON COLUMN common_mst.common_value2 IS '汎用マスタ値';

COMMENT ON COLUMN common_mst.update_datetime IS '更新日時';

COMMENT ON COLUMN common_mst.version IS 'バージョン';

INSERT INTO
        user_mst(
                user_id,
                password,
                user_name,
                secret_id,
                secret_answer,
                error_password,
                lock,
                authority
        )
VALUES
        (
                'user00000001',
                '$2a$10$m5CzxWKChQWZd464NOHLueG.sgoEfMASNwRZ6pQmN.k2wkFpiAHaS',
                'test',
                'com0001',
                '$2a$10$m5CzxWKChQWZd464NOHLueG.sgoEfMASNwRZ6pQmN.k2wkFpiAHaS',
                '0',
                'FALSE',
                '0'
        );

INSERT INTO
        common_mst(
                common_id,
                common_group,
                common_name,
                common_value1,
                common_value2
        )
VALUES
        (
                'com0001',
                'secret_question',
                '秘密の質問',
                '好きな食べ物は？',
                '好きな食べ物は？'
        );

INSERT INTO
        common_mst(
                common_id,
                common_group,
                common_name,
                common_value1,
                common_value2
        )
VALUES
        (
                'com0002',
                'secret_question',
                '秘密の質問',
                '好きなアーティストは？',
                '好きなアーティストは？'
        );

INSERT INTO
        common_mst(
                common_id,
                common_group,
                common_name,
                common_value1,
                common_value2
        )
VALUES
        (
                'com0003',
                'secret_question',
                '秘密の質問',
                '昔、飼っていたペットの名前は？',
                '昔、飼っていたペットの名前は？'
        );

```

</div></details>

### swagger.v1.yml

<details><div>

```
openapi: 3.0.0
info:
  title: TmpSpringBoot
  version: '1.0'
  description: |-
    TmpSpringBootプロジェクト
      REST APIを主流としたSpring Bootの基本プロジェクトです。

    # API共通仕様
    ## 排他制御
    * 排他制御方式は、楽観排他とする。
    * 排他制御はDB更新時に毎回行う。
    * 楽観排他制御は以下のように行う。
      1. 取得系API実行時  
         DBに格納されているバージョン情報を返却する。
      2. 更新API実行時  
         取得系API実行時に返却したバージョン情報をリクエストパラメータに含める。  
         DB更新条件にリクエストパラメータで渡されたバージョン情報を含め、  
         バージョン情報が一致する場合のみ更新が行われるようにする。  
         更新時、バージョンを1インクリメントする。  
         キーが見つからなかったことによるエラーとバージョン不一致によるエラーを区別する。
    ## 入力パラメータ／バリデーションチェック
      * リクエストはStringで受け取り、必要に応じて型変換を行う。
      * 値がnullの場合は必須項目チェック以外のバリデーションチェックを行わない。
      * バリデーション結果がNGだった場合、HTTPステータスコード400、結果コード101を返却する。
        * 詳細な情報についてはレスポンスボディの'optional.badParameterList'に格納する。
          
          | パラメータ | 説明                                               |
          | ---------- | -------------------------------------------------- |
          | name       | パラメータ名                                       |
          | value      | 入力値                                             |
          | message    | エラーメッセージ(Springが生成したメッセージを格納) |

    ## 更新系APIのDB更新条件
      1. リクエストパラメータなし または 値がnullの場合  
         * DB更新なし
      2. その他の値の場合  
         * リクエストパラメータでDBを更新する。
    ## 結果コード一覧

    | 結果コード | 説明                                           |
    | ---------- | ---------------------------------------------- |
    | 000        | 正常                                           |
    | 101        | リクエストパラメータ不正                       |
    | 102        | ユーザIDまたはパスワードが不正                 |
    | 103        | 認証情報不正(ユーザ権限がない)                 |
    | 104        | ID作成に失敗                                   |
    | 105        | 取得、更新、削除対象のデータが存在しない       |
    | 106        | 既に同一キーが存在するデータを作成しようとした |
    | 107        | 排他制御が行われた                             |
    | 108        | 外部との通信に失敗した                         |
    | 109        | 不正なDB処理を検知した                         |
servers:
  - url: 'http://localhost:8080'
    description: APサーバ
  - url: 'http://localhost:8003'
    description: MOCKサーバ
tags:
  - name: 認証機能（login）
    description: 認証機能を担うAPI
  - name: ユーザ管理機能（account）
    description: ユーザ管理機能を担うAPI
paths:
  /user/login:
    post:
      summary: API001 - ログイン機能
      description: |
        ## 概要
        SpringSecurityの認証機能を利用してログイン処理を行う。

        ## 処理詳細
        1. リクエストパラメータチェックを行う。

           | No | パラメータ | 説明       | 型     | 文字種     | 必須 | デフォルト値 | 最小文字数 | 最大文字数 | パターン |
           |----|------------|------------|--------|------------|------|--------------|------------|------------|----------|
           | 1  | userId     | ユーザID    | String | 半角英数字 | ○    | -            | 12          | 12         | -        |
           | 2  | password   | パスワード | String | 半角英数字   | ○    | -            | 1          | 20         | -        |

        2. 「user_mst」テーブルからリクエストパラメータのユーザIDを検索条件に該当レコードを取得する。
           * 「user_mst」テーブルに該当レコードがなかった場合は404エラーとする。
           * 該当レコードのアカウントロックがTrueの場合は403エラーとする。

        3. リクエストパラメータのパスワードと2で取得したレコードのパスワードが一致するか確認する。
           * リクエストパラメータのパスワードをハッシュ化して比較する。
           * 一致の場合、以下の通りリクエストパラメータのユーザIDと「user_mst」テーブルのユーザIDが一致するレコードを更新する。

           | 論理名               | 物理名          | 導出元                               |
           | -------------------- | --------------- | ------------------------------------ |
           | ユーザID             | user_id         | 更新しない                           |
           | アカウントロック     | lock            | 0                                    |
           | 更新日時             | update_datetime | 現在日時                             |
           | バージョン           | version         | リクエストパラメータのバージョン + 1 |

           * 不一致の場合、以下の処理をする。
              - 2で取得したログイン失敗回数をカウントアップする。
              - 2で取得したログイン失敗回数が上限を超えた場合、2で取得したログイン失敗回数を0にして、2で取得したアカウントロックをTrueにする。
              - 以下の通りリクエストパラメータのユーザIDと「user_mst」テーブルのユーザIDが一致するレコードを更新する。

           | 論理名               | 物理名          | 導出元                               |
           | -------------------- | --------------- | ------------------------------------ |
           | ユーザID             | user_id         | 更新しない                           |
           | パスワード間違え回数 | error_password  | 2で取得したログイン失敗回数          |
           | アカウントロック     | lock            | 2で取得したアカウントロック          |
           | 更新日時             | update_datetime | 現在日時                             |
           | バージョン           | version         | リクエストパラメータのバージョン + 1 |

        4. SpringSecurityの認証処理を行う。
        5. 2で取得した「user_mst」テーブルのレコードを返却する。
      requestBody:
        content:
          application/json:
            schema:
              description: ''
              type: object
              properties:
                userId:
                  type: string
                  minLength: 12
                  maxLength: 12
                password:
                  type: string
                  minLength: 1
                  maxLength: 20
              required:
                - userId
                - password
            examples:
              example:
                value:
                  userId: user00000001
                  password: string
        description: ''
      responses:
        '200':
          description: 正常
          content:
            application/json:
              schema:
                type: object
                properties:
                  resultCode:
                    type: string
                    description: 結果コード
                    enum:
                      - '000'
                  optional:
                    $ref: '#/components/schemas/User'
              examples:
                example:
                  value:
                    resultCode: '000'
                    optional:
                      userId: user0000001
                      userName: example
                      author: ユーザ
                      version: 0
          headers:
            JSESSIONID:
              schema:
                type: string
              description: セッションID
        '400':
          $ref: '#/components/responses/400'
        '403':
          $ref: '#/components/responses/403_login'
      operationId: postLogin
      parameters:
        - schema:
            type: string
          in: header
          name: Content-Type
          description: application/json
      x-internal: false
      tags:
        - 認証機能（login）
    parameters: []
  /user/logout:
    post:
      summary: API002 - ログアウト機能
      description: |-
        ## 概要
        SpringSecurityの認証機能を利用してログアウト処理を行う。

        ## 処理詳細
        1. SpringSecurityのログアウト処理を行う。
        2. レスポンスを返却する。
      responses:
        '200':
          $ref: '#/components/responses/200'
      operationId: postLogout
      security:
        - Cookie: []
      tags:
        - 認証機能（login）
    parameters: []
  /user/search:
    get:
      summary: API003 - ユーザ検索機能
      description: |
        ## 概要
        「user_mst」テーブルから検索を行う。
        ## 処理詳細
        1. リクエストパラメータチェックを行う。
          
           | No | パラメータ | 説明             | 型      | 文字種     | 必須 | デフォルト値 | 最小文字数(最小値) | 最大文字数(最大値) | パターン(正規表現) |
           |----| ---------- | ---------------- | ------- | ---------- | ---- | ------------ | ------------------ | ------------------ | ------------------ |
           | 1  | userId     | ユーザID         | String  | 半角英数字 |      |              | 12                 | 12                 |                    |
           | 2  | userName   | ユーザ名         | String  | 半角英数字 | -    |              | 1                  | 100                |                    |
           | 3  | lock       | アカウントロック | Boolean | 半角数字   |      |              | 0                  | 1                  |                    |
           | 4  | authority  | 権限             | Byte    | 半角数字   |      | 0            |                    |                    | [01]               |
           | 5  | orderBy    | ソート項目       | Integer | 半角数字   | -    | 1            | -                  | -                  | [1234] ※1           |
           | 6  | ascOrDesc  | 昇順／降順       | Integer | 半角数字   | -    | 1            | -                  | -                  | [12]               |
           | 7  | limit      | 最大取得数       | Integer | 半角数字   | -    | 100          | (1)                | (2147483647)       | -                  |
           | 8  | offset     | オフセット       | Integer | 半角数字   | -    | 0            | (0)                | (2147483647)       | -                  |

           ※1 ソート項目は、1：ユーザID, 2：ユーザ名, 3：アカウントロック, 4：権限。
        2. 管理者ユーザか確認する。
           * 管理者ユーザ以外の場合、403エラーとする。
        3. 「user_mst」テーブルからリクエストパラメータのユーザID, アカウントロック, 権限を検索条件に該当レコードを取得する。
           * リクエストパラメータがnullの項目は検索条件に適用しない。
        4. 取得した「user_mst」テーブルの該当レコードを返却する。
      parameters:
        - name: userId
          in: query
          description: ユーザID
          schema:
            type: string
            minLength: 12
            maxLength: 12
        - name: lock
          in: query
          description: アカウントロック
          required: false
          schema:
            type: boolean
            minLength: 1
            maxLength: 20
        - name: authority
          in: query
          description: 権限
          required: false
          schema:
            type: integer
            enum:
              - 0
              - 1
            default: 0
        - name: orderBy
          in: query
          description: ソート項目
          schema:
            type: integer
            enum:
              - 1
              - 2
              - 3
            default: 1
        - name: ascOrDesc
          in: query
          description: '1: 昇順, 2: 降順'
          schema:
            type: integer
            enum:
              - 1
              - 2
            default: 1
        - name: limit
          in: query
          description: 最大取得数
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 2147483647
        - name: offset
          in: query
          description: オフセット
          schema:
            type: integer
            default: 0
            minimum: 0
            maximum: 2147483647
      responses:
        '200':
          description: 正常
          content:
            application/json:
              schema:
                type: object
                properties:
                  resultCode:
                    description: 結果コード
                    type: string
                    enum:
                      - '000'
                  optional:
                    $ref: '#/components/schemas/UserList'
        '400':
          $ref: '#/components/responses/400'
      operationId: getAccountSearch
      security:
        - Cookie: []
      tags:
        - ユーザ管理機能（account）
    parameters: []
  /account:
    get:
      summary: API004 - ユーザ詳細検索機能
      description: |
        ## 概要
        「user_mst」テーブルから詳細情報を取得し、返却する。
        ## 処理詳細 
        1. ログイン時のユーザIDをキーに「user_mst」テーブルからレコードを取得する。
           * 「user_mst」テーブルに該当レコードがなかった場合は404エラーとする。
        2. 取得した「user_mst」テーブルのレコードを返却する。
      responses:
        '200':
          description: 正常
          content:
            application/json:
              schema:
                type: object
                properties:
                  resultCode:
                    type: string
                    description: '結果コード (000: 正常)'
                    enum:
                      - '000'
                  optional:
                    $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/400'
        '404':
          $ref: '#/components/responses/404'
      operationId: getAccount
      security:
        - Cookie: []
      parameters: []
      tags:
        - ユーザ管理機能（account）
    put:
      tags:
        - ユーザ管理機能（account）
      summary: API005 - ユーザ編集機能
      description: |
        ## 概要
        「user_mst」テーブルの更新を行う。
        ## 処理詳細
        1. リクエストパラメータチェックを行う。

           | No  | パラメータ | 説明       | 型     | 文字種     | 必須 | デフォルト値 | 最小文字数(最小値) | 最大文字数(最大値) | パターン(正規表現) |
           | --- | ---------- | ---------- | ------ | ---------- | ---- | ------------ | ------------------ | ------------------ | ------------------ |
           | 1   | password     | パスワード   | String | 半角英数字 | -    |           | 1                 | 20                 |               |
           | 2   | userName     | ユーザ名   | String | 半角英数字 | -    |           | 1                 | 100                 |               |
           | 3   | version     | バージョン   | Short | 半角数字 | ○    | 0          |                  |                  |               |

        2. ログイン時のユーザIDをキーにDBにレコードが存在するか確認する。
           * レコードが存在しない場合、404エラーとする。
        3. リクエストパラメータの「version」と「user_mst」テーブルの「version」が一致するか確認する。
           * 不一致の場合、409エラーとする。
        4. 以下の通りログイン時のユーザIDと「user_mst」テーブルのユーザIDが一致するレコードを更新する。

           | 論理名               | 物理名          | 導出元                                     |
           | -------------------- | --------------- | ------------------------------------------ |
           | ユーザID           | user_id        | 更新しない           |
           | パスワード           | password        | リクエストパラメータのパスワード               |
           | ユーザ名           | user_name        | リクエストパラメータのユーザ名               |
           | 更新日時           | update_datetime        | 現在日時            |
           | バージョン           | version        | リクエストパラメータのバージョン + 1          |

        5. レスポンスを返却する。
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userName:
                  type: string
                  minLength: 1
                  maxLength: 20
                  description: ユーザ名
                password:
                  type: string
                  description: パスワード
                  minLength: 1
                  maxLength: 20
                version:
                  type: integer
                  description: バージョン
                  maximum: 32767
                  minimum: 0
              required:
                - version
      responses:
        '200':
          $ref: '#/components/responses/200'
        '400':
          $ref: '#/components/responses/400'
        '403':
          $ref: '#/components/responses/403'
        '404':
          $ref: '#/components/responses/404'
        '409':
          $ref: '#/components/responses/409'
      operationId: putAccount
      security:
        - Cookie: []
    delete:
      tags:
        - ユーザ管理機能（account）
      summary: API006 - ユーザ削除機能
      description: |
        ## 概要
        ログイン時のユーザIDと「user_mst」テーブルのユーザIDが一致するレコードを削除する。  
        ## 処理詳細
        1. リクエストパラメータチェックを行う。

           | No | パラメータ | 説明       | 型     | 文字種       | 必須 | デフォルト値 | 最小文字数(最小値) | 最大文字数(最大値) | パターン(正規表現) |
           |----|------------|------------|--------|--------------|------|--------------|--------------------|--------------------|--------------------|
           | 1   | userId      | ユーザID | String |  半角英数字  | ○    |         | 12                 | 12                  |                 |
        2. ログイン時のユーザIDと「user_mst」テーブルのユーザIDが一致するレコードを削除する。
           * 削除対象のレコードが存在しない場合は404エラーとする。
        3. レスポンスを返却する。
      responses:
        '200':
          $ref: '#/components/responses/200'
        '404':
          $ref: '#/components/responses/404'
        '500':
          $ref: '#/components/responses/500'
      operationId: deleteAccount
      security:
        - Cookie: []
  /account/lock:
    put:
      tags:
        - ユーザ管理機能（account）
      summary: API007 - ユーザロック機能
      description: |
        ## 概要
        「user_mst」テーブルの更新を行う。
        ## 処理詳細
        1. リクエストパラメータチェックを行う。

           | No  | パラメータ | 説明       | 型     | 文字種     | 必須 | デフォルト値 | 最小文字数(最小値) | 最大文字数(最大値) | パターン(正規表現) |
           | --- | ---------- | ---------- | ------ | ---------- | ---- | ------------ | ------------------ | ------------------ | ------------------ |
           | 1   | userId     | ユーザID   | String | 半角英数字 | -    |           | 12                 | 12                 |               |
           | 2   | lock     | アカウントロック   | Boolean | 半角数字 | -    |           | 0                 | 1                 |               |
           | 3   | version     | バージョン   | Short | 半角数字 | ○    | 0          |                  |                  |               |

        2. 管理者ユーザか確認する。
           * 管理者ユーザ以外の場合、403エラーとする。
        3. リクエストパラメータのをキーにDBにレコードが存在するか確認する。
           * 情報が存在しない場合、404エラーとする。
        4. リクエストパラメータの「version」と「user_mst」テーブルの「version」が一致するか確認する。
           * 不一致の場合、409エラーとする。
        5. 以下の通りリクエストパラメータのと「user_mst」テーブルのが一致するレコードを更新する。

           | 論理名               | 物理名          | 導出元                                     |
           | -------------------- | --------------- | ------------------------------------------ |
           | ユーザID           | user_id        | 更新しない               |
           | アカウントロック           | lock        | リクエストパラメータのアカウントロック               |
           | バージョン           | version        | リクエストパラメータのバージョン + 1          |
        5. レスポンスを返却する。
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userId:
                  type: string
                  minLength: 12
                  maxLength: 12
                  description: ユーザID
                lock:
                  type: boolean
                  description: 権限
                version:
                  type: integer
                  description: バージョン
                  maximum: 32767
                  minimum: 0
              required:
                - version
      responses:
        '200':
          $ref: '#/components/responses/200'
        '400':
          $ref: '#/components/responses/400'
        '403':
          $ref: '#/components/responses/403'
        '404':
          $ref: '#/components/responses/404'
        '409':
          $ref: '#/components/responses/409'
      operationId: putAccountLock
      security:
        - Cookie: []
    parameters: []
  /signup:
    post:
      tags:
        - ユーザ管理機能（account）
      summary: API008 - ユーザ登録機能
      description: |
        ## 概要
        「user_mst」テーブルに登録を行う。
        ## 処理詳細
        1. リクエストパラメータチェックを行う。
          
           | No  | パラメータ   | 説明         | 型     | 文字種     | 必須 | デフォルト値 | 最小文字数(最小値) | 最大文字数(最大値) | パターン(正規表現) |
           | --- | ------------ | ------------ | ------ | ---------- | ---- | ------------ | ------------------ | ------------------ | ------------------ |
           | 1   | password     | パスワード   | String | 半角英数字 | 〇   |              | 1                  | 20                 |                    |
           | 2   | userName     | ユーザ名     | String | 半角／全角 | 〇   |              | 1                  | 100                |                    |
           | 3   | secretId     | 秘密の質問ID | String | 半角英数字 | 〇   |              | 7                  | 7                  |                    |
           | 4   | secretAnswer | 秘密の答え   | String | 半角英数字 | 〇   |              | 1                  | 200                |                    |

        2. 以下の通り「user_mst」テーブルに追加する。

           | 論理名               | 物理名          | 導出元                                                  |
           | -------------------- | --------------- | ------------------------------------------------------- |
           | ユーザID             | user_id         | ※1                                                     |
           | パスワード           | password        | リクエストパラメータのパスワードをハッシュ化した値      |
           | ユーザ名             | user_name       | リクエストパラメータのユーザ名                          |
           | 秘密の質問ID         | secret_id       | リクエストパラメータの秘密の質問ID                      |
           | 秘密の答え           | secret_answer   | リクエストパラメータの秘密の答え                        |
           | パスワード間違え回数 | error_password  | 0                                                       |
           | アカウントロック     | lock            | False                                                   |
           | 権限                 | authority       | 0                                                       |
           | 更新日時             | update_datetime | 設定しない ※デフォルト値のCURRENT_TIMESTAMPが設定される |
           | バージョン           | version         | 設定しない ※デフォルト値の0が設定される                 |

           ※1 通番でユーザIDを発行し、登録を行う。通番は「ID体系:user00000001~user99999999」とし、  
               「user_mst」テーブルから最大のユーザIDを取得し、インクリメントした値を格納する。
        3. 登録したレコードのユーザIDを返却する。
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userName:
                  type: string
                  minLength: 1
                  maxLength: 20
                  description: ユーザ名
                password:
                  type: string
                  minLength: 1
                  maxLength: 20
                  description: パスワード
                secretQuestionId:
                  type: string
                  minLength: 7
                  maxLength: 7
                  description: 秘密の質問
                secretAnswer:
                  type: string
                  minLength: 1
                  maxLength: 20
                  description: 秘密の答え
              required:
                - userName
                - password
                - secretQuestionId
                - secretAnswer
        description: ''
      responses:
        '200':
          description: 正常
          content:
            application/json:
              schema:
                type: object
                properties:
                  resultCode:
                    description: '結果コード (000: 正常)'
                    type: string
                    enum:
                      - '000'
                  optional:
                    type: object
                    properties:
                      userId:
                        description: ユーザID
                        type: string
        '400':
          $ref: '#/components/responses/400'
        '500':
          $ref: '#/components/responses/500'
      operationId: postSignup
    parameters: []
  /question:
    get:
      summary: API009 - 秘密の質問検索機能
      operationId: getQuestion
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  resultCode1:
                    type: string
                  optional:
                    type: object
                    properties:
                      questionList:
                        type: array
                        items:
                          type: object
                          properties:
                            commonId:
                              type: string
                            commonGroup:
                              type: string
                            commonName:
                              type: string
                            commonValue1:
                              type: string
                            commonValue2:
                              type: string
      description: |
        ## 概要
        「common_mst」テーブルから秘密の質問を取得する。
        ## 処理詳細

        1. 「common_mst」テーブルから「secret_question」を汎用マスタグループの検索条件に該当レコードを取得する。
        2. 取得した 「common_mst」テーブルのレコードを返却する。
      tags:
        - ユーザ管理機能（account）
    put:
      summary: API010 - パスワード再設定機能
      operationId: putQuestion
      responses:
        '200':
          $ref: '#/components/responses/200'
        '400':
          $ref: '#/components/responses/400'
        '404':
          $ref: '#/components/responses/404'
      description: |
        ## 概要
        秘密の質問と答えの一致を確認し、パスワードを変更する。
        ## 処理詳細
        1. リクエストパラメータチェックを行う。  

           | No | パラメータ     | 説明           | 型      | 文字種     | 必須 | デフォルト値 | 最小文字数(最小値) | 最大文字数(最大値) | パターン(正規表現) |
           |----|----------------|---------------|---------|------------|------|--------------|--------------------|--------------------|--------------------|
           | 1  | userId         | ユーザID      | String  | 半角英数字  | 〇    | -            | 12                 | 12                 | -                  |
           | 2  | newPassword       | 新パスワード | String  | 半角英数字    | 〇    | -            | 1                  | 20                  | -               |
           | 3  | secretQuestionId| 秘密の質問    | String  | 半角英数字   | 〇    | -            | 7                  | 7                  | -                  |
           | 4  | secretAnswer   | 秘密の答え     | String | 半角 / 全角   | 〇    | -            | 1                  | 200                 | -                  |

        2. ユーザIDをキーに「user_mst」テーブルからユーザ情報を取得する。
           * ユーザ情報が存在しない場合、404エラーとする。
        3. リクエストパラメータの秘密の質問、答えがユーザ情報の秘密の質問、答えと一致するか確認する。
           * 不一致の場合、404エラーとする。
        4. パスワードをリクエストパラメータの新パスワードに変更する。
           * 変更する際、新パスワードをハッシュ化する。
        5. レスポンスを返却する。
      tags:
        - ユーザ管理機能（account）
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userId:
                  type: string
                  minLength: 12
                  maxLength: 12
                newPassword:
                  type: string
                  minLength: 1
                  maxLength: 20
                secretQuestionId:
                  type: string
                  minLength: 7
                  maxLength: 7
                secretAnswer:
                  type: string
                  minLength: 1
                  maxLength: 20
              required:
                - userId
                - newPassword
                - secretQuestionId
                - secretAnswer
components:
  securitySchemes:
    Cookie:
      name: JSESSIONID
      type: apiKey
      in: cookie
      description: ''
  responses:
    '200':
      description: 正常
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
                description: '結果コード (000: 正常)'
                enum:
                  - '000'
    '400':
      description: Bad Request - リクエストパラメータに問題があります
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
                description: '結果コード (1001: リクエストパラメータエラー)'
                enum:
                  - '101'
              optional:
                type: object
                properties:
                  errorParameterList:
                    description: 不正パラメータリスト
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          description: パラメータ名
                          type: string
                        value:
                          description: 値
                          type: string
                        message:
                          description: メッセージ
                          type: string
    '403':
      description: Forbidden - アクセス権がありません
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
                description: '結果コード (1003: 認証情報なし, 1004: 権限エラー)'
                enum:
                  - '103'
                  - '104'
    '404':
      description: Not Found - データが存在しません
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
                description: '結果コード (1005: 正常)'
                enum:
                  - '105'
    '405':
      description: Method Not Allowed - パスが違います
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
    '409':
      description: Conflict - 排他制御が行われました
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
                description: '結果コード (1007: 正常)'
                enum:
                  - '107'
    '500':
      description: Internal Server Error - 予期せぬエラーが発生しました
    403_login:
      description: Forbidden - 認証情報が不正です
      content:
        application/json:
          schema:
            type: object
            properties:
              resultCode:
                type: string
                description: '結果コード (1002: ログイン情報不正)'
                enum:
                  - '102'
  schemas:
    UserList:
      type: object
      properties:
        total:
          type: integer
        userList:
          type: array
          items:
            $ref: '#/components/schemas/User'
    User:
      type: object
      properties:
        userId:
          type: string
        userName:
          type: string
        authority:
          type: string
        version:
          type: integer
  parameters: {}
  requestBodies: {}


```

</div></details>

### Dockerfile

```
FROM maven:3.6.3-jdk-11 AS builder
WORKDIR /tmp
COPY ./tmpSpringBoot/src ./src
COPY ./tmpSpringBoot/pom.xml .
RUN mvn package
FROM openjdk:11.0-jdk
COPY --from=builder /tmp/target/tmpSpringBoot-1.0.jar /app/tmpSpringBoot-1.0.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/tmpSpringBoot-1.0.jar"]
```

- `maven:3.6.3-jdk-11` コンテナを作成後、`/tmp` をワークディレクトリに指定し、資材を配置して、`mvn package` コマンドでビルドします。
- `jar` ファイルにビルドした資材を `openjdk:11.0-jdk` コンテナに配置し、 `java -jar jarファイル名` コマンドでアプリケーションを起動します。

### docker-compose.yml

```swagger:create_table.sql
version: '3'

services:
  app:
    build: ./app
    container_name: tmp-spring-boot
    ports:
      - 8080:8080
    depends_on:
      - db
    environment:
      - POSTGRES_USER=testUser
      - POSTGRES_PASSWORD=testPassword
      - POSTGRES_URL=jdbc:postgresql://db:5432/test
  db:
    image: postgres:14
    container_name: test-postgres
    ports:
      - 15432:5432
    volumes:
      - db-store:/var/lib/postgresql/data
      - ./init/sql:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_USER=testUser
      - POSTGRES_PASSWORD=testPassword
      - POSTGRES_DB=test
  swagger-editor:
    image: swaggerapi/swagger-editor
    container_name: "swagger-editor"
    ports:
      - "8001:8080"
  swagger-ui:
    image: swaggerapi/swagger-ui
    container_name: "swagger-ui"
    ports:
      - "8002:8080"
    volumes:
      - ./swagger/swagger.v1.yml:/openapi.yml
    environment:
      SWAGGER_JSON: /openapi.yml
  swagger-api:
    image: stoplight/prism:3
    container_name: "swagger-api"
    ports:
      - "8003:4010"
    command: mock -h 0.0.0.0 /openapi.yml
    volumes:
      - ./swagger/swagger.v1.yml:/openapi.yml
volumes:
  db-store:
```

- app：[Dockerfile](#dockerfile) のコンテナを作成する。PostgreSQL使用のため環境変数も定義する。
- db：**PostgreSQL バージョン14**コンテナを作成する。
- swagger-editor：**Swagger Editor**（YAMLやJSONなどの形式でAPI仕様を記載出来るエディタ）コンテナを作成する。
- swagger-ui：**Swagger UI**コンテナを作成する。
- swagger-api：**Mockサーバ**コンテナを作成する。

## 起動方法

### 資材取得

- 実行コマンド
```
git clone https://github.com/ryomeblog/docker-spring-psql
cd docker-spring-psql
```

- 実行例
```
$ git clone https://github.com/ryomeblog/docker-spring-psql
Cloning into 'docker-spring-psql'...
remote: Enumerating objects: 120, done.
remote: Counting objects: 100% (120/120), done.
remote: Compressing objects: 100% (71/71), done.
remote: Total 120 (delta 33), reused 120 (delta 33), pack-reused 0
Receiving objects: 100% (120/120), 61.34 KiB | 2.27 MiB/s, done.
Resolving deltas: 100% (33/33), done.
$ cd docker-spring-psql
~/docker-spring-psql$
```

### サービス実行

- コマンド
```
docker-compose up -d
```

- 実行例
```
$ docker-compose up -d
Creating network "docker-spring_default" with the default driver
Pulling db (postgres:14)...
14: Pulling from library/postgres
31b3f1ad4ce1: Pull complete
dc97844d0cd5: Pull complete
9ad9b1166fde: Pull complete
286c4682b24d: Pull complete
1d3679a4a1a1: Pull complete
5f2e6cdc8503: Pull complete
0f7dc70f54e8: Pull complete
a090c7442692: Pull complete
81bfe40fd0f6: Pull complete
8ac8c22bbb38: Pull complete
96e51d1d3c6e: Pull complete
667bd4154fa2: Pull complete
87267fb600a9: Pull complete
...省略
Creating swagger-api     ... done
Creating test-postgres  ... done
Creating swagger-ui      ... done
Creating swagger-editor  ... done
Creating tmp-spring-boot ... done
```

## 実行確認

2022/10/15 追記

以下のファイルは[GitHub](#github)の `others` フォルダ内にあります。
- talend-api-tester.json
- test.a5dblist

### tmp-spring-boot（Springプロジェクト）

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/fa1d7022-befd-705b-f611-7ed2c2ff0942.png)
1. Google Chromeの拡張機能 [Talend API Tester](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=ja) を開く
    - 持っていない人は [Talend API Tester](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=ja) を追加してください。
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/e3cc0a9e-60d1-9dcd-eca9-b4c4a3bd1454.png)
2. `Import API Tester repository` を選択する
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/d512f571-16de-8eae-0778-295fa131f49f.png)
3. `talend-api-tester.json` を 選択し、`Global environments` と `swagger-api`, `tmp-spring-boot` にチェックを付けて、 `Import` を押下する
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/597327d5-e1d0-9545-2e3b-e137ecd29c79.png)
4. `tmp-spring-boot > 認証機能（login） > API001-ログイン機能` を開く
![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/580d63a5-569d-1581-3e32-f53ed1829998.png)
5. `Send` を押下する
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/98b919a8-7203-cc7e-10c1-299b90d6403c.png)
6. 200応答が返ってくれば成功


### test-postgres（PostgreSQL14）
1. `A5:SQL Mk-2` を開く
    - 持っていない人は [A5:SQL Mk-2 (x64)](https://apps.microsoft.com/store/detail/a5sql-mk2-x64/9NSBB9XTJW86?hl=en-us&gl=us) を追加してください。
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/e7c56ced-9be2-0259-9482-566d54f2d757.png)
2. `データベース > データベースの追加と削除` を選択
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/9057cb36-75c0-2ca5-6577-92ab308eb037.png)
3. `インポート` を押下する
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/226beeb6-a9e0-a38d-dfa5-7b50d2d221e1.png)
4. `test.a5dblist` を選択し、 `localhost/test` が追加されたことを確認する
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/bbd5d589-ab1c-233c-8460-48080a8343f7.png)
5. `localhost > test` に接続する
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/340f3f27-084b-d0c8-b09d-bbba2e205262.png)
6. パスワードに `testPassword` と入力し、 `接続` を押下する
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/bdab1b97-a81d-2f52-1da0-98d2e4dfd18b.png)
7. `localhost > test > public > user_mst` が確認できれば成功

### Swagger Editor
1. [localhost:8001](http://localhost:8001/) にアクセスする
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/a2ce1e95-a6a5-5fd5-db2b-b4cdd6ecc97c.png)
2. Swagger Editorページが表示されれば成功


### Swagger UI
1. [localhost:8002](http://localhost:8002/) にアクセスする
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/be0ee99f-2c70-f1e6-abee-6301b1caa97e.png)
2. `swagger.v1.yml` と記載内容が同じSwagger UIページが表示されれば成功


### Swagger API

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/fa1d7022-befd-705b-f611-7ed2c2ff0942.png)
1. Google Chromeの拡張機能 [Talend API Tester](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=ja) を開く
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/bbf65eec-7396-0f91-90d1-d1f84aa85477.png)
2. `swagger-api > 認証機能（login） > API001-ログイン機能` を開く
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/3021a089-7d59-0693-65b0-00509e9a9569.png)
3. `Send` を押下する
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449867/902c6204-a8e5-07e3-4bd3-06ea697ab0ab.png)
4. 200応答が返ってくれば成功


## GitHub

GitHubにソースコードを公開しています。

https://github.com/ryomeblog/qiita/tree/master/DockerでSpring×PostgreSQL環境構築

## 関連
https://qiita.com/ryome/items/54d23052babda2d88bf6

https://qiita.com/ryome/items/12d8fb5f64acfbdd22e9

https://qiita.com/ryome/items/873938ca6734adec1c11

https://qiita.com/ryome/items/c9fded9da60bce0f0b94

## 参考文献
- [SwaggerでRESTful APIの管理を楽にする](https://qiita.com/disc99/items/37228f5d687ad2969aa2)
- [Spring Bootアプリケーションのコンテナ化を試してみた](https://qiita.com/saitoshi/items/a931399e81e63e8e4f1e)
- [DockerでSwagger環境簡単構築！](https://qiita.com/A-Kira/items/3d17396c7cc98873e29d)

2022/10/15 追記
`Talend API Tester` と `A5:SQL Mk-2` については以下のリンクが参考になります。
- [Chrome拡張のTalend API TesterでAPIテスト](https://qiita.com/ktr1211/items/c16cb4f99f91b998af46)
- [A5:SQL Mk-2 とは](https://a5m2.mmatsubara.com/help/start.html)