メンチカツには醤油でしょ!!

ITエンジニア徒然 (AWS/Java/JavaScript/Google Spreadsheets/Jenkins/Mac/外部コミュニティ・勉強会レポ)

ThymeleafのJava 8 LocalDateTime対応で選択変数式th:objectとth:textの実装方法

Java8のLocalDateTimeをThymeleafで使うには

build.gradleにthymeleaf-extras-java8timeを足して
@Configurationの付いたクラス(ThymeleafConfigとか)にjava8TimeDialect()を実装して
htmlのth:textでは"${#temporals.format(myDatetime}, 'yyyy/MM/dd HH:mm')}"と書く方法が定番かと思われます。

(LocalDateTimeでもLocalDateでもLocalTimeでも同じですね。)

選択式変数と組み合わせて使う場合にはちょっと工夫が必要でした。
選択式変数とはforeachのようなシチュエーションで使う構文で、下記のようにth:objectで指定するとループ内ではアスタリスクで指定できるという利点があります。

<table><tr th:each="record : ${records}" th:object="${record}">
  <td th:text="*{value}">
</tr></table>

環境

* Spring Boot 1.5.6
* Thymeleaf 2.1.5
* Thymeleaf Module for Java 8 Time API compatibility 2.1.0.RELEASE

実装

build.gradle (抜粋)
dependencies {
  compile('org.springframework.boot:spring-boot-starter-web')
  compile('org.springframework.boot:spring-boot-starter-thymeleaf')
  compile('org.thymeleaf.extras:thymeleaf-extras-java8time:2.1.0.RELEASE')
  // 略
}
ShowListController.java (抜粋)
  @RequestMapping(method = RequestMethod.GET)
  public String index(Model model) {

    List<MyBean> myList = query(); // 何かしらの検索処理.

    model.addAttribute("myList", myList);

    return "showList";
  }
MyBean.java (抜粋)
@Data
public class MyBean implements Serializable {
  private int myNumber;
  private int myString;
  private LocalDateTime myDatetime;
}
showList.html (抜粋)
<tr th:each="myBean : ${myList}" th:object="${myBean}">
  <td th:text="*{myNumber}"></td>
  <td th:text="*{myString}"></td>
  <!-- こう書くとダメ -->
  <td th:text="${#temporals.format(*{myDatetime}, 'yyyy/MM/dd HH:mm')}"></td>
  <!-- これはまぁOK -->
  <td th:text="${#temporals.format(myList.myDatetime, 'yyyy/MM/dd HH:mm')}"></td>
  <!-- こう書くと良い -->
  <td th:text="*{#temporals.format(myDateTime, 'yyyy/MM/dd HH:mm')}"></td>
</tr>

ダメな方の実装だと
There was an unexpected error (type=Internal Server Error, status=500).
Exception evaluating SpringEL expression: "#temporals.format(*{myDatetime}, 'yyyy/MM/dd HH:mm')" (showList:46)
ってエラーが出てStacktraceは
org.springframework.expression.spel.SpelParseException: Expression [#temporals.format(*{myDatetime}, 'yyyy/MM/dd HH:mm')] @46: EL1070E: Problem parsing left operand
って感じです。

チュートリアルをちょっと変更する感じだと、こう書きがちなんですが、
これは慣れの問題ですかね。

ちなみに
"#{temporals.format(myDatetime, 'yyyy/MM/dd HH:mm')}"
と書いても表示は
??temporals.format_ja??
とか表示されてダメです。(まぁこれは当然ですね)

間違えて
"${#temporals.format(myDatetime, 'yyyy/MM/dd HH:mm')"
と書くと
java.lang.IllegalArgumentException: Cannot apply format on null
になったり、こねくり回してると
org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method format(null,java.lang.String) on null context object
が出たりしますが、
*{#temporals.format(myDateTime, 'yyyy/MM/dd HH:mm')}"
が正解です。

参考

masatoshitada.hatenadiary.jp

qiita.com