Spring BootでOptional型オブジェクトをThymeleaf側で処理する

Spring Bootの画面側のThymeleafで、ただのJavaオブジェクトとOptional型のオブジェクトを空判定(nullチェック)しながら、オブジェクト内データを処理・表示するサンプルコードについて説明しています。

※ 本ページはプロモーションが含まれています。

このページの目的と動作環境

最終更新日:2023/9/27

本ページでは、Spring Bootのサーバサイド(コントローラークラス等)からOptional型のオブジェクト(インスタンス)を画面側のThymeleafに渡し、ThymeleafでOptional型オブジェクト(インスタンス)を処理する方法とサンプルのプログラミングコードについて説明しています。

Optional型オブジェクトは中身のデータがnull(Empty,空)の場合もあるので、Thymeleaf側でOptional型オブジェクト内データのnullチェックの判定をして、nullでないならデータを処理して表示しています。

◾️動作環境とバージョン情報です
OS:macOS Big Sur(バージョン11.7.7)
Spring Bootバージョン:3.1
Java:Java17(OpenJDK17 Eclipse Temurin(Adoptium))
開発環境:Eclipse(Pleiades All in One、Java Full Edition版)

Spring Bootプロジェクトを作成する

まず、Spring BootプロジェクトはEclipseのSpringスタータープロジェクトで作成しました。
EclipseでのSpring Bootプロジェクトの作り方については別ページで説明しているので、そちらを参考にしてください。
Sprint Boot2で初めてのJavaのウェブアプリ開発
Spring Boot3 + MySQL(MariaDB)のウェブ開発入門 - Spring Boot3系のプロジェクトを作成する

今回のSpring Bootプロジェクトで使う依存関係ライブラリは、Spring Boot Devtools、Spring Web、Lombok、Thymeleaf、検証の5つです(検証は必要ないかも)。今回のSpring BootプロジェクトではDBを使わないので、Spring Data JPA,H2 Database,MySQL Driverなどのライブラリは使用しません。

Spring Bootのサーバサイド。コントローラークラスを作成する

まず、Spring Bootのサーバサイドのコントローラークラスを作成します(クラス名は適当でHogeOptionalController.java)

package com.example.demo;

import java.time.LocalDateTime;
import java.util.Optional;

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Controller
@RequestMapping("/")
public class HogeOptionalController {

  //テスト用のPOJO(データクラス)
  @Data
  @NoArgsConstructor
  @AllArgsConstructor
  public class HogeData {
    private Integer id;
    private String data;
    private LocalDateTime ldt;
    private boolean isDeleted;
  }

  //Optional型のオブジェクトを画面側(HTML・Thymeleaf)に返して、Thymeleaf側でOptional型オブジェクトを処理する。
  @GetMapping("/hoge1")
  public String hoge1(Model model) {

    //Optional内のHogeDataオブジェクトは存在する(isDeletedはfalse)
    Optional<HogeData> optHogeData = Optional.of(new HogeData(1, "テストデータ", LocalDateTime.now(), false));
    model.addAttribute("optHogeData", optHogeData);

    //Optional内のHogeDataオブジェクトは存在する(isDeletedはtrue)
    Optional<HogeData> optHogeData2 = Optional.of(new HogeData(2, "テストデータ2", LocalDateTime.now(), true));
    model.addAttribute("optHogeData2", optHogeData2);

    //Optionalの中身は空(Empty)
    Optional<HogeData> optHogeData3 = Optional.ofNullable(null);
    model.addAttribute("optHogeData3", optHogeData3);

    return "hoge1";
  }

  /*
   * サーバサイドでOptionalからデータを取得して画面側(Thymeleaf)に返す。
   * 画面側ではただのPOJOオブジェクト(HogeData)を処理する。
   */
  @GetMapping("/hoge2")
  public String hoge2(Model model) {

    //Optionalの中にHogeDataオブジェクトがある場合
    Optional<HogeData> optHogeData = Optional.of(new HogeData(1, "テストデータ", LocalDateTime.now(), false));
    //ここではOptionalの中にHogeDataオブジェクトがある事は分かっているからget()メソッドで中身を取得しても問題ないが、中のオブジェクトが空の場合を考慮してorElse()メソッドで中身を取得する。また、中のオブジェクトが空の場合、ここではnullを返すようにしているが、何か適当なHogeDataのオブジェクトをnewして返してもいい。
    //model.addAttribute("hogeData", optHogeData.get());
    model.addAttribute("hogeData", optHogeData.orElse(null));

    //Optionalの中は空(Empty)の場合
    Optional<HogeData> optHogeData2 = Optional.ofNullable(null);
    model.addAttribute("hogeData2", optHogeData2.orElse(null));

    return "hoge2";
  }
}

コントローラークラスには2つのメソッド(hoge1とhoge2)を作成しています(あとJava POJOのデータクラスHogeDataも)。

hoge1()メソッドは、HogeDataオブジェクトを包んだOptional型のオブジェクトを3つ作成して、それを画面側(HTML)のThymeleafに返して、Thymeleafで3つのOptionalオブジェクトを処理してHTMLを生成・表示するようにします。
ただ、3つのOptionalオブジェクトのうちの1つ(optHogeData3)は、中身が空(null)です。

hoge2()メソッドは、2つのHogeDataオブジェクトを包んだOptional型のオブジェクトを作り、そのあとすぐにOptionalオブジェクトから中身のHogeDataオブジェクトを取り出し、そのHogeDataオブジェクトを画面側のThymeleafに返しています。
ただ、Optionalオブジェクトの1つ(optHogeData2)は、中身が空(null)です。

hoge1()とhoge2()の違いは、OptionalオブジェクトをThymeleafに渡してThymeleaf側でOptionalオブジェクトを処理するか、それともサーバサイドでOptionalオブジェクトから中身のJavaデータオブジェクトを取得しThymeleafに渡して、Thymeleaf側でJavaデータオブジェクトを処理するかです。
正直どちらのやり方が正しいとかは無いので、どちらでも良いと思います。(個人的にどちらの方法でもできる事を試したかっただけなので。)

(広告)AmazonでSpring Boot3(バージョン3系)の初心者向け入門書を探す!本でSpring Bootプログラミング開発を体系的に勉強する!

画面側(HTML)のThymeleafを作成して、ThymeleafでOptional型オブジェクトを処理する

次に、上で説明したコントローラークラス(HogeOptionalController.java)のhoge1()メソッドが返すThymeleafファイルを作成します。
(*Thymeleafファイルは、Spring Bootプロジェクトのsrc/main/resources/templatesディレクトリ下にhoge1.htmlファイルを作成する。)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ThymeleafでOptional型オブジェクトを処理する</title>
</head>
<body>
<h1>ThymeleafでOptional型オブジェクト(optHogeData)を処理する</h1>

<!-- HTMLソースで見れるコメント文 -->
<!--/*--> HTMLソースで見れないThymeleafのコメント文 <!--*/-->

<div>
<h3>■optHogeDataの処理(optHogeDataはデータがあり、optHogeData.isDeletedフラグはfalse)</h3>
<h4>・${optHogeData.isEmpty}でOptional型のoptHogeDataの中身が空かをチェック。 ${optHogeData.empty}でも同じ。</h4>
<p th:if="${optHogeData.isEmpty}">optHogeDataは空です。</p>
<h4>・${optHogeData.isPresent}でoptHogeDataにデータが存在するかをチェック。 ${optHogeData.present}でも同じ。</h4>
<p th:if="${optHogeData.isPresent}">optHogeDataは存在します。</p>
<h4>・AND条件。optHogeDataにデータが存在していて、かつ、optHogeDataのisDeletedフラグがfalseなら、optHogeData内のオブジェクトを取得して表示する。</h4>
<p th:if="${optHogeData.isPresent} and ${optHogeData.get.isDeleted eq false}">optHogeDataは存在していて、削除フラグもfalseです。</p>
<p th:if="${optHogeData.isPresent} and ${optHogeData.get.isDeleted eq false}" th:text="${optHogeData.get.data}" ></p>
<p th:if="${optHogeData.isPresent} and ${optHogeData.get.isDeleted eq false}" th:object="${optHogeData.get()}" >
id:[[*{id}]]<br>
data:[[*{data}]]<br>
ldt:[[*{#temporals.format(ldt, 'yyyy年MM月dd日 HH:mm:ss')}]]<br>
isDeleted:[[*{isDeleted}]]<br>
</p>
<h4>・th:blockタグで条件判断して、条件判断とhtmlタグ出力を分ける。</h4>
<th:block th:if="${optHogeData.isPresent} and ${optHogeData.get.isDeleted eq false}">
<p>optHogeDataは存在していて、削除フラグもfalseです。</p>
</th:block>
<h4>・三項演算子も使って、optHogeData.isDeletedがture/falseによって表示する文言を変更する。</h4>
<p th:if="${optHogeData.isPresent}" th:text="${optHogeData.get.isDeleted eq false} ? *{optHogeData.get.data} : 'データは存在するが、isDeletedがtrueです。'"></p>
</div>
<hr>

<div>
<h3>■optHogeData2の処理(optHogeData2はデータがあり、optHogeData.isDeletedフラグはtrue)</h3>
<h4>・${optHogeData2.isEmpty}でOptional型のoptHogeData2の中身が空かをチェック</h4>
<p th:if="${optHogeData2.isEmpty}">optHogeData2は空です。</p>
<h4>・${optHogeData2.isPresent}でoptHogeData2にデータが存在するかをチェック</h4>
<p th:if="${optHogeData2.isPresent}">optHogeData2は存在します。</p>
<h4>・optHogeData2にデータが存在していて、かつ、optHogeData2のisDeletedフラグがfalseなら、optHogeData2内のオブジェクトを取得して表示する。</h4>
<p th:if="${optHogeData2.isPresent} and ${optHogeData2.get.isDeleted eq false}">optHogeDataは存在していて、削除フラグもfalseです。</p>
<p th:if="${optHogeData2.isPresent} and ${optHogeData2.get.isDeleted eq false}" th:text="${optHogeData2.get.data}" ></p>
<p th:if="${optHogeData2.isPresent} and ${optHogeData2.get.isDeleted eq false}" th:object="${optHogeData2.get()}" >
id:[[*{id}]]<br>
data:[[*{data}]]<br>
ldt:[[*{#temporals.format(ldt, 'yyyy年MM月dd日 HH:mm:ss')}]]<br>
isDeleted:[[*{isDeleted}]]<br>
</p>
<h4>・三項演算子も使って、optHogeData2.isDeletedがture/falseによって表示する文言を変更する。</h4>
<p th:if="${optHogeData2.isPresent}" th:text="${optHogeData2.get.isDeleted eq false} ? *{optHogeData2.get.data} : 'データは存在するが、isDeletedがtrueです。'"></p>
</div>
<hr>

<div>
<h3>■optHogeData3の処理(optHogeDataはデータが無い(Empty))</h3>
<h4>・${optHogeData3.isEmpty}でOptional型のoptHogeData3の中身が空かをチェック</h4>
<p th:if="${optHogeData3.isEmpty}">optHogeData3は空です。</p>
<h4>・${optHogeData3.isPresent}でoptHogeData3にデータが存在するかをチェック</h4>
<p th:if="${optHogeData3.isPresent}">optHogeData3は存在します。</p>
<h4>・optHogeData3にデータが存在していて、かつ、optHogeData3のisDeletedフラグがfalseなら、optHogeData3内のオブジェクトを取得して表示する。</h4>
<p th:if="${optHogeData3.isPresent} and ${optHogeData3.get.isDeleted eq false}">optHogeDataは存在していて、削除フラグもfalseです。</p>
<p th:if="${optHogeData3.isPresent} and ${optHogeData3.get.isDeleted eq false}" th:text="${optHogeData3.get.data}" ></p>
<p th:if="${optHogeData3.isPresent} and ${optHogeData3.get.isDeleted eq false}" th:object="${optHogeData3.get()}" >
id:[[*{id}]]<br>
data:[[*{data}]]<br>
ldt:[[*{#temporals.format(ldt, 'yyyy年MM月dd日 HH:mm:ss')}]]<br>
isDeleted:[[*{isDeleted}]]<br>
</p>
<h4>・三項演算子も使って、optHogeData3.isDeletedがture/falseによって表示する文言を変更する。</h4>
<p th:if="${optHogeData3.isPresent}" th:text="${optHogeData3.get.isDeleted eq false} ? *{optHogeData3.get.data} : 'データは存在するが、isDeletedがtrueです。'"></p>
</div>

</body>
</html>

動作確認は、Spring Bootを起動してにブラウザで「http://localhost:8080/hoge1」にアクセスすればできます。
ThymeleafでOptional型オブジェクトをnullチェックして表示

サーバーサイドのHogeOptionalController.javaのhoge1()メソッドは、3つのOptional型インスタンスを返したので、この3つをThymeleaf(hoge1.html) 側で処理しています。

Optional型オブジェクトの中にデータ(HogeDataオブジェクト)が存在して(nullではない)、かつ、HogeDataオブジェクトのisDeleted(削除フラグ)がfalseの場合、HogeDataオブジェクトのデータをHTML表示しています。

Optional型オブジェクト内のデータのnullチェック(空かどうか、データが存在するかのnull判定)や表示処理はいくつかのサンプルコードで試しています。
このサンプルコード内で簡単な説明コメントを書いているので、そちらも参考にしてください。

画面側のThymeleafを作成して、Thymeleafでオブジェクト(Java POJO)を処理する

次に、コントローラークラス(HogeOptionalController.java)のhoge2()メソッドが返すThymeleafファイル(hoge2.html)を作成します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleafでオブジェクトを処理する</title>
</head>
<body>
<h1>Thymeleafでオブジェクト(HogeData)を処理する</h1>

<div>
<h3>■hogeDataオブジェクトの処理(hogeDataはデータがある)</h3>
<h4>・hogeDataオブジェクトがnullでなければ、hogeDataオブジェクトのデータを表示する。</h4>
<p th:if="${hogeData != null}">
<span>id : [[${hogeData.id}]]</span><br>
<span>data : [[${hogeData.data}]]</span><br>
<span>ldt : [[${hogeData.ldt}]]</span><br>
<span>isDeleted : [[${hogeData.isDeleted}]]</span><br>
</p>
<h4>・これも同じ。th:blockタグを使っている。</h4>
<th:block th:if="${hogeData != null}">
<p>
<span>id : [[${hogeData.id}]]</span><br>
<span>data : [[${hogeData.data}]]</span><br>
<span>ldt : [[${hogeData.ldt}]]</span><br>
<span>isDeleted : [[${hogeData.isDeleted}]]</span><br>
</p>
</th:block>
</div>
<hr>

<div>
<h3>■hogeData2オブジェクトの処理(hogeData2はnull)</h3>
<h4>・hogeData2オブジェクトがnullでなければ、hogeData2オブジェクトのデータを表示する。</h4>
<p th:if="${hogeData2 != null}">
<span>id : [[${hogeData2.id}]]</span><br>
<span>data : [[${hogeData2.data}]]</span><br>
<span>ldt : [[${hogeData2.ldt}]]</span><br>
<span>isDeleted : [[${hogeData2.isDeleted}]]</span><br>
</p>
</div>
<hr>

</body>
</html>

動作確認は「http://localhost:8080/hoge2」でできます。

サーバーサイドのHogeOptionalController.javaのhoge2()メソッドは、2つのHogeDataオブジェクト(1つはnullですが)を返したので、この2つをThymeleaf(hoge2.html)で処理しています。

Thymeleafでは、単純なif文でHogeDataオブジェクトのnullチェック(null判定)をして、nullでない場合はHogeDataオブジェクトを表示しています。