Spring Boot、PostgreSQL、MyBatis、Thymeleafを使って簡単なWebアプリをつくってみる

この記事では、Javaで簡単なサーバサイドレンダリング型の初歩的なWebアプリを作ってみます。

この時、最もポピュラーで手軽なのが**SpringBoot＋Thymeleaf**の構成です。

Thymeleafはタイムリーフと読みます。
詳細は[【初級編】Thymeleafの使い方 ](https://knowledge.spirers.jp/article/development/detail/1868 "タイトル")など参照。

:::note
Spring Boot + JSPの構成も可能ではありますが非推奨となっています。また、Thymeleafは純粋なHTMLと非常によく似ているため、技術的な難易度も高くありません。ということでThymeleafを使うことにします。
:::

データベースは、今回はPostgreSQLを扱います。

データベースマッパー（簡単にいうと、プログラミング言語とデータベースの橋渡しをするもの）はMyBatisを利用します。
データベースマッパーないしはO/Rマッパーは様々なものがあり、それぞれに利点があります。詳しくは下記を参照。

https://qiita.com/keita_ide78/items/48fd62e9505d2ddad51f

今回MyBatisを使う理由としては、
Mybatis Generatorというプラグインを使いたいから、というのがひとつにあります。
のちに具体的に触れますが、Mybatis Generatorとは、
データベースのテーブルに対する基本的なCRUD処理を行うコードを
コマンドを実行するだけで自動生成してくれる優れものです。

# 技術構成

以下で実装していくことにします。

+ Java17
+ Spring Boot
+ Maven
+ PostgreSQL14
+ MyBatis
+ MyBatis Generator
+ Thymeleaf

JavaとMaven、postgreSQLのインストール手順はググれば沢山出てくるので省略します。
事前にインストールしておいてください。

なお私の開発環境はWindows11です。

# 使用ツール
+ VSCode
+ A5:SQL Mk2（Microsoft storeからでも入手可）
（インストール手順は省略）

Eclipseを使う方も多い印象ですが、
GUIがゴチャゴチャしているのと、動きが重いことが多いので私はVScode推しです。

なお、VSCodeには下記の拡張機能をインストールしてください。
+ Spring Initializr Java Support
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/41eda82b-3b35-6323-ca01-97e5536bc6de.png)

+ Spring Boot Tools
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/1941e55b-563b-2fa8-8419-827deefd365e.png)


# 1:まずはプロジェクトを作成します

とりあえず、まずはMavenプロジェクトを作成してみましょう。

VSCodeを開き、ctrl +shift + pで、Spring Initializr Create a Maven Projectを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/284d0c8d-c88b-01e0-8ffe-c351f8b89a4e.png)

以降、Mavenのバージョンは任意（とりあえず最新を選択しておく）、
グループID、アーティファクトID任意、
パッケージングタイプはjar、warどちらでも可。
Dependencyはいったん何も選択しないでおく（この時点でThymeleafやらlombokやら選択しても勿論OKではあるが、ここでは敢えて後で手動で追記してみることにします）。


# 2:pom.xmlに依存性を追加していきます

:::note info
pom.xmlとは、mavenの設定ファイル（のひとつ）です。

pom.xmlでは特に、dependencyタグが大変重要な意味を持っています。

dependencyとは、簡単にいえばそのプロジェクトが依存する（利用する）モジュールのことで、このファイルで一元管理することができます。
:::

dependenciesタグの中に、いろいろと追記していきましょう。

まずはWebアプリをつくるので、spring-boot-starter-webを追記します。
（Webアプリを作るために必要な各種APIを使うために必須です。）

```xml
		<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
```

次にThymeleafを追加します。

```xml
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
```

（任意）
lombokも便利なライブラリとしてテンプレなので追加します。@Setter、@Getterなどというアノテーションが使えるようになり、SetterやGetterなどの長ったらしい記述をしなくても済むようになります。（なくても良いが、あった方がコーディングがラクです）

```xml
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
```

### 補足

私の場合はSpring Initializr Create a Maven Projectを実行した段階で
pom.xmlに下記が記述されていましたが、実行環境によっては下記が記述されないようです。
記述がない場合はdependenciesタグやpropertiesタグと同階層に追記してください。

```xml
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.1</version>
		<relativePath/>
	</parent>
```

ない場合、後でSpring Boot起動時に、下記エラーが発生します（することがあります）。
```
ConfigServletWebServerApplicationContext : Exception encountered 
during context initialization - cancelling refresh attempt: java.lang.IllegalArgumentException: Could not find class
```

# 3:画面をつくろう

次に超簡単ですが、画面をつくってみます。
resources配下にtemplatesフォルダをつくり、その配下にHTMLファイルを作成します。
（SpringBootではデフォルトでresources/templates以下のHTMLが参照されます）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/60e21897-a6f7-e5ee-51c8-75e0fb673729.png)

```html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" lang="ja">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>

<body>
    Hello World!
</body>
</html>

```

以上で、最小限の画面がつくれました。


# 4:Controllerをつくろう

次に、HTTPリクエストの受け口となるコントローラクラスが必要です。

controllerパッケージをつくり、その配下にいい感じの名前のコントローラクラスをつくってみましょう。

簡単に言えば、所定のURLにきたらなんらかの処理をしてテンプレートエンジンに渡すという内容を記載していきます。

今回はとりあえずDemoController.javaにします。

  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/59372d28-103c-b929-ff7a-d988464df004.png)

中身はこんな感じにします。

```java
package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class DemoController {
    
    @GetMapping("/demo_home")
    public String home(){
        return "demo";
    }
}

```

これにより、HTTPメソッド：GETで、/demo_homeにリクエストが飛ばされたときに、homeメソッドが発火し、demo.htmlが表示されることになります。

戻り値にHTMLファイル名を書くのが特徴的なポイントです。
return "demo.html"と書く必要はありません。


# 5:起動してみる

Spring Bootを起動してみましょう。

@SpringBootApplication　というアノテーションがついているクラスがSpring Bootのエントリポイントになります。
ここのRunをクリックしましょう。（デバッグ実行したい場合はDebugの方を押下）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/38cce6e4-e146-4151-603a-75939691ad5a.png)

起動すると、ターミナルにこんな感じに表示されます。
（ターミナルを表示させるショートカット：ctrl + @）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/c7f46b8b-a9cb-a8a7-fc57-3ac11a62e088.png)

Webブラウザで実際の画面を確認しましょう。
なお、デフォルトではSpring Bootはポート番号は8080を使用しますので、以下のURLを入力します。

http://localhost:8080/demo_home

こんなふうに表示されればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/14abdab3-caf4-7a71-e624-86f9f37a0547.png)

※なお、Webブラウザで直リンクすると、HTTPメソッドはGETになります。POSTやPUTにはなりません。

:::note info
通常は、ポート番号の定義は設定ファイルに記述しておくべきものです。
Spring Bootの設定ファイルは（通常）、application.propertiesになります。
以下を書いておきましょう。
```
server.port=8080
```

:::

# 6:コントローラからHTMLに値を渡したい

ここまでは、単純に画面を出すだけでした。
今度はコントローラから画面に値を渡して表示させたいと思います。

コントローラにモデルを追記しましょう。
addAttribute()で、第一引数と第二引数がキー:バリューという感じでセットします。

```java
package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.ui.Model;

@Controller
public class DemoController {
    
    @GetMapping("/demo_home")
    public String home(Model model){
        model.addAttribute("msg", "こんにちは世界");
        return "demo";
    }
}
```

:::note info
HTML側は、th:text="${msg}"の箇所がポイントです。
「th:○○」というのをHTMLタグの中に書くのがThymeleafの基本構文です。
th:textは最も基本的な使い方です。
:::

```html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" lang="ja">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>

<body>
    <p th:text="${msg}"></p>
</body>
</html>
```

Spring Bootを再起動して、もう一度URLを叩いてみます。

こんなふうに表示されればOK。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/47961036-3edb-4748-d872-441b67ca55fa.png)

# 7:データベースに接続したい

ここまでの登場人物は、単にJavaとHTMLだけの話でした。

次にデータベースと接続し、その値を画面に描画したいと思います。

### pomに追記

まずはpom.xmlの依存性に以下を追記しましょう。

PostgreSQL。

  ```xml
        <dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
```

MyBatis
```xml
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>3.0.1</version>
		</dependency>
  ```

  pom.xmlに上記を追記しただけでSpring Bootの起動を試みると、
  APPLICATION FAILED TO START
  Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
  というエラーになります。

  データベースの接続情報がないためです。まずはデータベースを作成しましょう。

### 7-1:データベース作成

  コマンドプロンプトでpsqlログインし、データベースを作成します。
  以下は一例です。

:::note warn
なお、PostgreSQLの場合、識別子に大文字は設定すべきではありません！
:::
  
  ```
  CREATE DATABASE sampledb TEMPLATE template0 ENCODING  'UTF8' LC_COLLATE 'ja_JP.UTF-8' LC_CTYPE 'ja_JP.UTF-8';
  ```

データベースを作成したら、念のため入ってみましょう。
psql -U ユーザ名 -d sampledb

### 7-2:A5:SQL Mk2でデータベース接続してみよう

コマンドラインでデータベース操作するのも悪くはありませんが、便利なGUIツールがありますので、A5:SQL Mk2を使いましょう！

「データベース」タブ　-> 「データベースの追加と削除」-> 「追加」という流れでクリックし、
「PostgreSQL（直接接続）」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/58cf2e39-710b-18d3-c0dd-562270bd0f49.png)

あとは接続先情報を入力してします。（PostgreSQLのデフォルトのポートは5432なので、理由がなければそのままでOK）

### 7-3:application.propertiesにデータベースの接続情報を追記

さて、データベースが作成できました。

次にSpringBootの設定ファイルに接続情報を追記します。
spring.datasource.driver-class-name=org.postgresql.DriverはマルコピでOKです。
その他はそれぞれの環境に合わせて記述してください。

```
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/demodb
spring.datasource.username=secret
spring.datasource.password=secret
```

これでSpring Bootの起動には成功するようになるはずです。

# 8:テーブルからレコードを取得して画面に表示させよう

続いて、テーブルからレコードを取得して画面に表示させるように実装してみたいと思います。

### 8-1:まずはテーブル定義をつくろう

データベースのテーブルを作成します。

手でちまちまとテーブル作成クエリを書くのはミスが起きやすいので、これもA5でやってしまいましょう。

「ファイル」タブ　-> 「新規」-> 「ER図」という流れでクリックします。

プロジェクト名は任意、RDMMS種類はPostgreSQLを選択します。

次にオブジェクトを記載していきます。
エンティティの追加をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/a7a5474b-d081-9bd4-df42-a01277b93aac.png)

以下、スクショの要領で、カラム定義を記載しましょう。
スクショは、eventというテーブルをつくるものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/f1ffb0c6-1bad-484e-2577-c5d0d0b8aecc.png)

### 8-2:テーブルをつくろう（create tableの実行）

テーブル定義ができたなら、テーブルを実際にデータベースにつくってみましょう。

A5でER図を開いた状態で、「ER図」タブ -> 「DDLを作成する」とするとこんなポップアップが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/0dffe08c-064d-1b31-9547-6f45346b6b91.png)

基本的にはこのまま「DDL生成」ボタンを押下してOKです。

こんな感じにsqlが出力されればOKです。

```sql
/*
  << 注意！！ >>
  BackupToTempTable, RestoreFromTempTable疑似命令が付加されています。
  これにより、drop table, create table 後もデータが残ります。
  この機能は一時的に $$TableName のような一時テーブルを作成します。
  この機能は A5:SQL Mk-2でのみ有効であることに注意してください。
*/

-- 出来事
-- * BackupToTempTable
drop table if exists event cascade;

-- * RestoreFromTempTable
create table event (
  event_id serial
  , date timestamp
  , year smallint not null
  , month smallint not null
  , day smallint
  , event varchar(60) not null
  , event_detail varchar(300)
  , user_add varchar(30) not null
  , user_update varchar(30)
  , registration_time timestamp default current_timestamp not null
  , update_time timestamp
  , delete_flg boolean default false not null
  , constraint event_PKC primary key (event_id)
) ;

comment on table event is '出来事';
comment on column event.event_id is 'イベントID';
comment on column event.date is '年月日';
comment on column event.year is '年';
comment on column event.month is '月';
comment on column event.day is '日';
comment on column event.event is '出来事';
comment on column event.event_detail is '詳細';
comment on column event.user_add is '追加者';
comment on column event.user_update is '最終更新者';
comment on column event.registration_time is '登録日時';
comment on column event.update_time is '更新日時';
comment on column event.delete_flg is '削除フラグ';

```

:::note info
なお、BackupToTempTable、RestoreFromTempTableというのは、テーブルを削除して作り直すとき、削除前のテーブルのレコードデータを一時テーブルに退避させ、復元するものです。

レコードがまだ何も登録されていない段階では特に関係ありませんので、気にする必要はありません。
:::


実行するデータベースを選択し、実行しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/42465ae9-aa58-b343-b5e6-1f0c702d2807.png)

実行したいクエリを範囲選択して、ctrl + enterで実行できます。

### 8:3 MyBatis Generatorで基本的なCRUDコードを作成しよう

Mybatisには、MyBatis Generatorというプラグインがあります。
単一のテーブルに対する基本的なCRUD処理を行うコードを自動的に生成してくれるので、コーディングの手間が省けるという便利な代物です。

習うより慣れろでとりあえず使ってみましょう！

今度は、build->plugins配下に以下を追記します。

```xml
<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <configuration>
        <configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
        <overwrite>true</overwrite>
        <includeAllDependencies>true</includeAllDependencies>
    </configuration>
</plugin>
```

このうち、
```
<configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
```
ここに注目してください。

configurationFileというのは、MyBatis Generatorの設定ファイルになります。
どういうふうにコードを生成するのか、作成対象のテーブルは何か、などです。

上記の場合は/src/main/resources配下に、generatorConfig.xmlを配置し、記載することになります。
（**自動生成を行うための**設定ファイルなので、これは手動で書く必要があります）

#### generatorConfig.xmlの記述例

以下は例です。こんな感じに書きましょう。

+ jdbcConnectionタグ
+ javaModelGeneratorタグのtargetPackageとtargetProject
+ sqlMapGeneratorタグのtargetPackageとtargetProject
+ javaClientGeneratorタグのtargetPackageとtargetProject
+ tableタグ
を編集してください。他の設定はお好み次第。

なお、sqlMapGeneratorとjavaClientGeneratorのtargetPackageは同じにしてください。
同じにしない場合、実行時に、org.apache.ibatis.binding.BindingExceptionというエラーが発生します。

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
  <context id="PostgresTables" targetRuntime="MyBatis3">
  
  <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
  <plugin type="org.mybatis.generator.plugins.MapperAnnotationPlugin"/>

    <!-- スキーマ情報を取得する DB への接続設定 -->
    <jdbcConnection 
    	driverClass="org.postgresql.Driver"
        connectionURL="jdbc:postgresql://localhost:5432/sampledb"
        userId="postgres"
        password="postgres">
    </jdbcConnection>

    <!-- SELECT 結果等を格納するドメインモデルを生成する設定 -->
    <javaModelGenerator targetPackage="com.example.demo.model" targetProject="パス\src\main\java">
      <property name="enableSubPackages" value="false" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>

    <!-- SQL 設定が記述された XML を生成する設定 -->
    <!-- targetPackageはxmlファイルはresourcesフォルダ下のMapperインタフェースと同じディレクトリ構造を指定する必要がある（模様） -->
    <sqlMapGenerator targetPackage="com.example.demo.mapper" targetProject="パス\src\main\resources">
      <!-- enableSubPackages：テーブルのスキーマごとにパッケージを作成するかどうかを指定 -->
      <property name="enableSubPackages" value="false" />
    </sqlMapGenerator>

    <!-- マッパークラスを生成する設定 -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.demo.mapper" targetProject="パス\src\main\java">
      <property name="enableSubPackages" value="false" />
    </javaClientGenerator>

    <!-- コードを生成するテーブルを指定 -->
    <table schema="public" tableName="event"></table>
  </context>
</generatorConfiguration>

```

#### コードを自動生成します

generatorConfig.xmlを書いたら、コマンドを実行します。

Mavenの設定ファイルはpom.xmlでしたね。pom.xmlのあるフォルダにcdし、以下のコマンドを実行してください。

```
mvn -e mybatis-generator:generate
```

ビルドエラーの場合は大抵、データベース名、ユーザ名、パスワード、テーブル名のミスです。
ビルドサクセスとなったら、ソースを確認してください。

:::note

以下のように1テーブルあたり4種類のファイルが作成されているはずです。

+ Event.java
+ EventMapper.java
+ EventMapper.xml
+ EventExample.java
※テーブルに複合主キーがある場合は5種類できます。
:::


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/dbfb32ef-720e-8483-92f2-8fd8c2a7ffdf.png)


:::note
テーブル名.javaはDTOクラスです。テーブルのカラムと対応してます。
:::

:::note
テーブル名Mapper.javaは、テーブルに対するCRUD処理を行うメソッドを定義しています。
例えば
insert()は登録処理、
selectByExample()は取得処理、
updateByExample()は更新処理、
deleteByExample()は削除処理
を行います。
:::

:::note
テーブル名Mapper.xmlは、EventMapper.javaと対になるもので、クエリを定義しています。
:::

:::note
テーブル名Example.javaは、クエリの条件指定に用います。
（select,update,deleteのときのwhereの指定）
:::

::: note warn

以上の自動生成されたファイルは、お作法として、原則として手動で変更することは避けるようにしてください！
テーブル定義を変更して再度自動生成した場合に、手動での変更分が消えてしまい、管理が煩雑になります。
:::


:::note warn
**ここでいったんSpring Bootを起動してみてください。**
起動が成功すれば一安心です！
:::

:::note warn
**トラブルシューティングメモ**
下記のエラーになった場合は、pomの依存関係のバージョンをとりあえず変更してみてください。
私はmybatis-spring-boot-starterの<version>3.0.1</version>でこけたので、<version>3.0.3</version>に変更したら通るようになりました。

```
2023-12-26T23:09:32.026+09:00  WARN 1960 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: java.lang.IllegalArgumentException: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String
2023-12-26T23:09:32.038+09:00  INFO 1960 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger :

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-12-26T23:09:32.060+09:00 ERROR 1960 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalArgumentException: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String
```
:::


### 8-4: テーブルからレコードを取得してみる

#### 8-4-1: レコード作成

さて、次にテーブルからレコードを取得してみたいと思います、、が、まだ何もレコードは登録されていない状態でしたね。

とりあえずなんでもいいのでレコードを登録したいと思います。A5の機能を使いダミーデータを投入しましょう。

黄色と黒のマークのボタンが、「テスト用ダミーデータ作成」のボタンになっていますのでポチります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/72f1bc14-9acd-e1bb-66b7-819a1213060c.png)

とりあえず終了値を99にして、100件つくることにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/7a59b33e-3cb3-035d-b266-ae28a0228c01.png)

#### 8-4-2: コントローラクラスにレコード取得処理を書く

先ほど作成したコントローラクラスに、テーブルからの取得処理を書きましょう。

以下、実装例です。

eventMapper.selectByExample()が取得を行う処理です。
（本来はtry-catchで囲うべきですが、ここでは紙幅のため省略します。）

```java
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.example.demo.mapper.EventMapper;
import com.example.demo.model.EventExample;

import org.springframework.ui.Model;

@Controller
public class DemoController {
    
    @Autowired
    EventMapper eventMapper;

    @GetMapping("/demo_home")
    public String home(Model model){
        var eventList = eventMapper.selectByExample(new EventExample());

        System.out.println(eventList.size());
        model.addAttribute("numOfEventList", eventList.size());
        return "demo";
    }
}

```

#### 8-4-2: 画面側も編集

次にHTMLも編集しましょう。取得件数を画面に表示したいとします。

```html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" lang="ja">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>

<body>
    <p th:text="${numOfEventList} + '件取得しました。'" />
</body>
</html>

```
#### 8-4-3: 起動

さて、これでSpring Bootを起動してみます。
以下のように表示されれば正解です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/f2d28f19-e8d9-2666-cc48-23315b720f42.png)

### 8-4-4: レコードも一覧表示させよう。

件数だけ出してもしょぼいので、レコードデータも出してみましょう。

#### Controller
以下、実装例です。

```java
    @GetMapping("/demo_home")
    public String home(Model model) {
        List<Event> eventList = new ArrayList<Event>();

        EventExample eventExample = new EventExample();
        eventExample.createCriteria()
            .andYearBetween((short) 11, (short) 20)
            .andMonthBetween((short) 11, (short) 20)
            ;
            eventExample.setOrderByClause("year, month, day asc");
        eventList = eventMapper.selectByExample(eventExample);

        System.out.println(eventList.size());
        model.addAttribute("numOfEventList", eventList.size());
        model.addAttribute("eventList", eventList);
        return "demo";
    }
```
new EventExampleに続く部分で、クエリの条件を指定しています。

EventExampleクラスには条件を指定するためのメソッドがありますが、
[カラム名][条件]というメソッドの命名規則になっています。

上記の場合、Yearが11～20かつMonthが11～20のレコードを取得する、という意味になります。
また、setOrderByClauseで、order byを指定しています。

:::note 
PostgreSQLでは、order byを指定しない場合、取得順はランダムになります。
そのため昇順/降順指定はしておいた方が動作が一定になり、望ましいです。
:::

#### 【文法的な補足】
なお、上記の条件生成とクエリ実行までを一文で書くとすると以下になります。
慣れるとこちらの方が読みやすいかもしれません。
```java
    @GetMapping("/demo_home")
    public String home(Model model) {
        List<Event> eventList = new ArrayList<Event>();
        eventList = eventMapper.selectByExample(
                new EventExample() {
                    {
                        createCriteria()
                            .andYearBetween((short) 11, (short) 20)
                            .andMonthBetween((short) 11, (short) 20);
                        setOrderByClause("year, month, day asc");
                    }
                });

        System.out.println(eventList.size());
        model.addAttribute("numOfEventList", eventList.size());
        model.addAttribute("eventList", eventList);
        return "demo";
    }
```
無名クラスとイニシャライザを使った記法になります。

無名クラスとは、匿名クラスとも呼ばれますが、「宣言」と「利用」を一度に行うものです。詳しくは[Javaの匿名クラス（無名クラス）の使い方](https://magazine.techacademy.jp/magazine/19027)を参照。

イニシャライザとは、（インスタンス生成時に実行される）初期化処理のブロックのことで、コンストラクタよりも前に実行されるものです。詳しくは[イニシャライザとは？コンストラクタとの関係も解説](https://workteria.forward-soft.co.jp/blog/detail/10106)を参照。

new EventExample()のあとに{（波かっこ）が2連続で出てきます。
1番目の波かっこは無名クラスのカッコです。
2番目の波かっこはイニシャライザのカッコです。
つまり、EventExampleを継承する無名クラスをつくり、その無名クラスの初期化処理としてcreateCriteria()とsetOrderByClause()を行っています。

#### HTML

```html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" lang="ja">

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>

<body>
    <p th:text="${numOfEventList} + '件取得しました。'" />

    <table border="1" style="border-collapse: collapse" id="table1">
        <thead>
            <tr>
                <th>年</th>
                <th>月</th>
                <th>日</th>
                <th>出来事</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="event : ${eventList}">
                <td th:text="${event.year}" align="right"></td>
                <td th:text="${event.month}" align="right"></td>
                <td align="right">
                    <th:block th:if="${event.day == null}">-</th:block>
                    <th:block th:unless="${event.day == null}">
                        <div th:text="${event.day}"></div>
                    </th:block>
                </td>
                <td th:text="${event.event}"></td>
            </tr>
        </tbody>
    </table>
</body>
</html>
```

HTMLでは、th:eachとすることで、ループをつくることができます。
dayカラムにはnot null制約がないので、もしnullだったら、ハイフンを表示するという条件分岐をth:ifにて行っています。

起動し、以下のように表示されれば正解です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/18bb0e9e-e4df-6448-b37a-baee2357c25c.png)

# 補足:実行したクエリをログに出力する方法

マッパーによって実行されたクエリを確認したい場合には、以下をapplication.propertiesに追記してください。
（パッケージとEventMapperのところはそれぞれの値に変える）
```
logging.level.com.example.demo.mapper.EventMapper=DEBUG
```
これでこの様に出力されると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180727/d999a2ce-0432-6baf-d4eb-8a89e394ea89.png)

# 補足:MapperクラスのメソッドのSelectiveの意味

詳しくは、[MyBatis GeneratorのExampleクラスなど解説](https://tech-blog.tsukaby.com/archives/145 "タイトル")など参照頂ければとは思いますが、ここでも軽く触れます。

テーブル名Mapper.javaには、「Selective」がつくメソッドとつかないメソッドがあります。
違いは、**nullに作用するかどうか**です。

例えばupdateByExample()の場合、引数として渡されたDTOに値をセットしていない（=null）フィールドがあった場合、nullに更新されます。
それに対してupdateByExampleSelective()の場合は、nullのフィールドは更新対象外となります。

値が入っているレコードに対して、敢えてnullに更新したいと設計にすることはあまりないため、基本的には「Selective」がつく方を使う機会の方が多いです。

# 補足:MyBatis Generatorで自動生成したコードで実現できないクエリはどうやって実装するのか？

MyBstis Generatorで自動生成されたコードは、あくまでも単一のテーブルに対する基本的なCRUDのみです。

テーブル結合など、複雑なクエリを実現したい場合は、Mapper.javaとそれに対応するxmlは手動でつくる必要があります。

===================================================

とりあえずここまでとします。