Spring Bootでデータを保存する-その1（登録）

# 背景・目的
[Spring Bootでデータベースへのアクセスを試してみた](https://qiita.com/zumax/items/7487b86f56ea12c31d44)では、DBアクセスを試してみました。
今回は、[Accessing JPA Data with REST](https://spring.io/guides/gs/accessing-data-rest)を試してみます。

# まとめ
下記に特徴を整理します

|特徴|説明|
|:--|:--|
|H2|・Java SQL データベース<BR>・高速、OSS、JDBC<BR>・組み込み、サーバ、インメモリDB<BR>・ブラウザベースのコンソール<BR>・スモールフットプリント、2.5MBのJarファイル|
|H2の主な特徴|・非常に高速なデータベースエンジン<BR>・オープンソース<BR>・Javaで書かれている<BR>・標準SQL、JDBC APIをサポート<BR>・組み込みおよびサーバーモード、クラスタリングのサポート<BR>・強力なセキュリティ機能<BR>・PostgreSQL ODBCドライバ<BR>・複数バージョンの同時実行|
|H2の接続モード|・埋め込みモード（JDBC を使用したローカル接続）<BR>・サーバー モード (TCP/IP 経由の JDBC または ODBC を使用したリモート接続)<BR>・混合モード（ローカル接続とリモート接続を同時に実行）|


# 概要
今回、RESTによりリクエストを受けて、H2データベースにデータを保存します。

## H2
[H2 Database Engine](https://www.h2database.com/html/main.html)を基に整理します。

> Welcome to H2, the Java SQL database. The main features of H2 are:

> - Very fast, open source, JDBC API
> - Embedded and server modes; in-memory databases
> - Browser based Console application
> - Small footprint: around 2.5 MB jar file size

- Java SQL データベース
    - 高速、OSS、JDBC
    - 組み込み、サーバ、インメモリDB
    - ブラウザベースのコンソール
    - スモールフットプリント、2.5MBのJarファイル

### Features
#### 主な特徴
- 非常に高速なデータベースエンジン
- オープンソース
- Javaで書かれた
- 標準SQL、JDBC APIをサポート
- 組み込みおよびサーバーモード、クラスタリングのサポート
- 強力なセキュリティ機能
- PostgreSQL ODBCドライバ
- 複数バージョンの同時実行

#### 追加機能
- ディスクベースまたはインメモリのデータベースとテーブル、読み取り専用データベースのサポート、一時テーブル
- トランザクション サポート (コミットされていない読み取り、コミットされた読み取り、繰り返し読み取り、スナップショット)、2 フェーズ コミット
- 複数の接続、行レベルのロック
- コストベースのオプティマイザー、複雑なクエリに遺伝的アルゴリズムを使用、管理不要
- スクロール可能で更新可能な結果セットのサポート、大きな結果セット、外部結果のソート、関数は結果セットを返すことができます
- 暗号化データベース（AES）、SHA-256パスワード暗号化、暗号化機能、SSL
#### SQL サポート
- 複数のスキーマ、情報スキーマのサポート
- 参照整合性 / カスケード制約、チェック制約による外部キー制約
- 内部結合と外部結合、サブクエリ、読み取り専用ビュー、インラインビュー
- トリガーと Java 関数 / ストアド プロシージャ
- XMLやロスレスデータ圧縮を含む多くの組み込み関数
- ラージオブジェクト（BLOB/CLOB）や配列を含む幅広いデータ型
- シーケンスと ID 列、生成された列 (関数ベースのインデックスに使用できます)
- ORDER BY、GROUP BY、HAVING、UNION、OFFSET / FETCH (PERCENT および WITH TIES を含む)、LIMIT、TOP、DISTINCT / DISTINCT ON (...)
- ウィンドウ関数
- ICU4Jライブラリのサポートを含む照合のサポート
- ユーザーとロールのサポート
- IBM DB2、Apache Derby、HSQLDB、MS SQL Server、MySQL、Oracle、PostgreSQL の互換モード。

#### セキュリティ機能
- SQLインジェクション問題に対する解決策が含まれています
- ユーザーパスワード認証にはSHA-256とソルトを使用します
- サーバー モード接続の場合、ユーザー パスワードはネットワーク経由でプレーン テキストで送信されることはありません (安全でない接続を使用している場合でも、これは TCP サーバーにのみ適用され、H2 コンソールには適用されません。また、データベース URL でパスワードを設定した場合にも適用されません)。
- すべてのデータベースファイル（データのバックアップに使用できるスクリプトファイルを含む）は、AES-128暗号化アルゴリズムを使用して暗号化できます
- リモートJDBCドライバはTLS経由のTCP/IP接続をサポートします。
- 内蔵ウェブサーバーはTLS経由の接続をサポートしています
- パスワードは文字列ではなく文字配列を使用してデータベースに送信できます。

#### その他の機能とツール
- フットプリントが小さい（約2.5 MB）、メモリ要件が低い
- 複数のインデックスタイプ（Bツリー、ツリー、ハッシュ）
- 多次元インデックスのサポート
- CSV（カンマ区切り値）ファイルのサポート
- リンクされたテーブルと組み込みの仮想「範囲」テーブルのサポート
- ステートメントをサポートEXPLAIN PLAN; 洗練されたトレースオプション
- パフォーマンスを向上させるために、データベースの終了を遅らせたり無効にしたりすることができます。
- オートコンプリート機能を備えた Web ベースのコンソール アプリケーション (多くの言語に翻訳)
- データベースはSQLスクリプトファイルを生成できる
- データベースの内容をダンプできるリカバリツールが含まれています
- 変数のサポート（たとえば、累計を計算するため）
- 準備されたステートメントの自動再コンパイル
- 少数のデータベースファイルを使用する
- データの整合性を保つために、各レコードとログエントリにチェックサムを使用します。
- 十分にテスト済み（高いコード カバレッジ、ランダム化されたストレス テスト）


### 接続モード
次の接続モードがサポートされています。

- 埋め込みモード（JDBC を使用したローカル接続）
- サーバー モード (TCP/IP 経由の JDBC または ODBC を使用したリモート接続)
- 混合モード（ローカル接続とリモート接続を同時に実行）


# 実践
## 前提
下記を前提としています。

- Mac
- VSCode
- Java 17
- Maven 3.9.8

## プロジェクトの作成
1. VSCodeを起動します
1. 表示>コマンドパレットをクリックします
1. 下記を選択します
    - Spring Initializrを選択し、3.3.1
    - Java
1. GroupIdを指定します
1. ArtifactIdを指定します
1. Package TypeにJarを指定します
1. Javaバージョンに17を指定します
1. DependencyにSpring Webを指定します
1. 作成後、Add Workspaceをクリックします
1. 追加されました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/7087c68a-4a7c-990d-10a4-9dae906e492e.png)

## pom.xmlの修正
1. dependenciesに下記を追加します
    ```
    <dependency>
        <groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>

	<dependency>
		<groupId>com.mysql</groupId>
		<artifactId>mysql-connector-j</artifactId>
		<scope>runtime</scope>
	</dependency>

    ```
## application.properties
1. H2 Databaseを利用するためにapplication.propertiesに下記を追加します。
    ```
    spring.application.name=rest-db
    spring.datasource.url=jdbc:h2:mem:testdb
    spring.datasource.driverClassName=org.h2.Driver
    
    spring.h2.console.enabled=true
    ```


## Create a Domain Object

1. Personクラスを作成します
1. 下記のコードを追加します
    ```
    package com.example.data.rest_db;
    
    import jakarta.persistence.Column;
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType; // Add this import statement
    import jakarta.persistence.Id;
    import jakarta.persistence.Table;
    
    @Entity
    @Table(name = "people")
    public class Person {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column
        private Long id;
        
        @Column(length = 50,nullable = false)
        private String firstName;
    
        @Column(length = 50,nullable = false)
        private String lastName;
    
        public Long getId() {
          return id;
        }
        public void setId(Long id) {
          this.id = id;
        }
        public String getFirstName() {
          return firstName;
        }
        public void setFirstName(String firstName) {
          this.firstName = firstName;
        }
        public String getLastName() {
          return lastName;
        }
        public void setLastName(String lastName) {
          this.lastName = lastName;
        }
    }
    ```
    - `@Entity`
        - JPAエンティティとして認識される
    - `@Table(name="people")`
        - エンティティクラスに割り当てられるテーブルを指定する
        - nameでテーブル名を指定（省略も可能）
    - `@Id`
        - プライマリーキーになる
    - `@Column`
        - フィールドに割り当てられるカラム名を指定する（省略した場合、フィールド名がそのままカラム名として使われる） 

## Create a Person Repository
1. com.example.data.rest_db.repositoriesパッケージを作成します
1. PersonRepositoryインタフェイスを作成します
1. 下記のコードを追加します
    ```
    package com.example.data.rest_db.repositories;
    
    import com.example.data.rest_db.Person;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface PersonRepository extends JpaRepository<Person, Long> {
    }
    ```
    - `@Repository`
        - データアクセスを表している

## Controllerを作成する
1. WebControllerクラスを作成します
1. 下記のコードを追加します
    ```
    package com.example.data.rest_db;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    
    import com.example.data.rest_db.repositories.PersonRepository;
    
    import jakarta.transaction.Transactional;
    
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.util.List; // Add this import statement
    import org.springframework.web.bind.annotation.RequestMethod;
    
    
    @Controller
    public class WebController {
      @Autowired
      PersonRepository repository;
    
      @RequestMapping("/")
      public ModelAndView index(
        @ModelAttribute("formModel") Person person,
        ModelAndView mav) {
    
        mav.setViewName("index");
        mav.addObject("title","Test page");
        mav.addObject("message","This is JPA sample content.");
        List<Person> list = repository.findAll();
        mav.addObject("people", list);
    
        return mav;
      }
      
      
      @RequestMapping(value = "/", method=RequestMethod.POST)
      @Transactional
      public ModelAndView form(
        @ModelAttribute("formModel") Person person,
        ModelAndView mav) {
    
        repository.saveAndFlush(person);
        return new ModelAndView("redirect:/");
      }
    }


    ```
    - `@Autowired`
        - PersonRepositoryインスタンスをフィールドに関連付ける
        - Beanオブジェクトに関連付けを行う
    - `findAllメソッド`
        - PersonRepositoryには、findAllのメソッドはない
        - 継承元のJpaRepositoryに用意されている
    - `@ModelAttribute`
        - エンティティクラスのインスタンスを自動的に用意する
    - `saveAndFlushメソッド`
        - 引数のエンティティを永続化する
        - 


## テンプレートの作成
1. index.htmlを作成します
1. 下記のコードを追加します
    ```
    <!DOCTYPE html>
    <html> 
    <head>
        <title>top page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css">
    </head>
    <body class="container">
      <h1 class="display-4 mb-4" th:text="${title}"></h1>
      <p th:text="${message}"></p>
      <form method="post" action="/" th:object="${formModel}">
        <div class="mb-3">
          <label for="name" class="form-label">FirstName</label>
          <input type="text" class="form-control" name="firstName" th:value="*{firstName}" />
        </div>
        <div class="mb-3">
          <label for="name" class="form-label"></label>LastName</label>
          <input type="text" class="form-control" name="lastName" th:value="*{lastName}" />
        </div>
        <div class="mb-3">
          <input type="submit" class="btn btn-primary" value="Create" />
        </div>
      </form>
    
      <table class="table">
        <thread>
          <tr>
            <th>firstName</th>
            <th>lastName</th>
          </tr>
        </thread>
        <tbody>
          <tr th:each="person : ${people}">
            <td th:text="${person.firstName}"></td>
            <td th:text="${person.lastName}"></td>
          </tr>
        </tbody>
      </table>
    </body>
    </html>
    ```

## ビルド
1. 下記のコマンドを実行し、ビルドします
    ```
    $ mvn clean install
    $ ls -l target/rest-db-0.0.1-SNAPSHOT.jar
    -rw-r--r--  1 XXXX  XXX  47717020  7 13 23:17 target/rest-db-0.0.1-SNAPSHOT.jar
    $
    ```
1. 下記のコマンドを実行します
    ```
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    ```

## 確認
1. ブラウザを開き、`localhost:8080`でアクセスします。表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/4f7c346e-cc1f-bdb8-311e-efa839e5f9fe.png)

1. 次にH2コンソールを開きます。localhost:8080/h2-console/にアクセスします。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/06d6edd1-77b0-fccb-ff4e-2d631a91ceda.png)

1.Test Connectionをクリックします。接続されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/9e8eeeac-5876-a8b0-14a5-531b415a2fb2.png)

1. 次にConnectをクリックします。クエリエディタのようなものが見えました。Peopeテーブルはすでに登録されているようです
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c81bc8db-2b3b-1dbb-370d-e5e49bf771f6.png)

1. SELECTを実行します。まだデータはありません
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/7cf0b517-b45e-06ce-849f-357163ffa4d3.png)

1. FirstNameとLastNameに入力し、Createをクリックします。登録されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/817c7ae1-4c64-d010-de69-7c682c59c990.png)

1. H2コンソールでも確認できました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/fe385072-d512-9fce-87ed-56666eb07702.png)

## Spring Bootを再起動
1. Spring Bootを再起動します
1. トップページにアクセスすると、登録したデータは消えています。これはメモリ上に保存しているからですね
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e1c09733-a654-68df-75d3-33f89bc8d635.png)

1. H2コンソールからも消えています
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/ed9c33d0-a456-3071-ea8c-384b6e578787.png)

# 考察
今回は、H2データベースを使ってローカルに立てたDBに対して登録を行いました。次回は更新や削除を試します。

# 参考
https://spring.io/guides/gs/accessing-data-rest

https://www.h2database.com/html/main.html