Spring-Boot でJava８日時を使うためには

この記事はリクルートライフスタイル [Advent Calendar 2015 - Qiita](http://qiita.com/advent-calendar/2015/recruitlifestyle) の23日目です。

こんにちは。現在、JavaでサーバサイドのAPIを作っている関根です。
Javaといえば最近はSpring Bootを使うのがトレンドですね。

## 始めに
Spring Boot はJava8 に対応しているのですが、
実は、Java8日時API（LocalDateTime, LocalDate, LocalTime）を使用して、
システムから入出力する際にはいくつか設定しなければならないんですよ。

この記事では、Spring BootでJava8 から追加された日時APIを使用する方法についてまとめたいと思います。

## 対象
今回、使用したフレームワークは以下です。

* Spring Boot
* Template Engines：Thymeleaf
* O/Rマッパー：Mybatis

Hibernate, Velocityとかは今回対象でないですので、ご注意を。


## 環境
試した環境は以下の通りです。

* Java 1.8.0_51
* Spring Boot 1.3.0.RELEASE
* MyBatis 3.3.0
* mybatis-spring 1.2.3
* Maven 3.0.5

## Jackson
`@RequestBody`, `@ResponseBody` 等のJSON変換に Jackson を使う場合は、以下の設定が必要です。
1. pom.xml にjackson-datatype-jsr310 を追加。

   ```xml:pom.xml
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.6.2</version>
        </dependency>
   ```
 
2.Configuration にObjectMapper を追加。 

```java:JacksonConfiguration
@Configuration
public class JacksonConfiguration {
  @Bean
  @Primary
  public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();

    // JSR310 Date and Time API 変換対応
    objectMapper.registerModule(new JavaTimeModule());
    return objectMapper;
  }
}
```

書式指定する場合は、`@JsonFormat` をつければOK。

```java
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate date;
```

## `@RequestParam`、`@PathValiable`
リクエストパラメータを受け取る場合には、とくに特別なことはしなくて構いません。
java.util.Dateのときと同じく、`@DateTimeFormat` をつけることで受け取ることができます。

```java:DemoRestController
  @RequestMapping(value = "/path/{pathDate}", method = RequestMethod.GET)
  public String path(
      @DateTimeFormat(pattern = "yyyyMMdd") @PathVariable LocalDate pathDate,
      @DateTimeFormat(pattern = "yyyyMMdd") @RequestParam LocalDate requestDate) {

    return "pathDate:" + pathDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
        + " requestDate:" + requestDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
  }
```

## MyBatis
MybatisでJava8 日時APIを使用するために、`BaseTypeHandler` を継承したクラスを作らなければなりません。
ここで`LocalDate`を扱うTypeHandlerクラスを見てみましょう。

1.`BaseTypeHandler` を継承したクラスを作成する。
    ジェネリックは`java.time.LocalDate` にする。

```java:LocalDateTypeHandler
package com.example.typeHandler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;

/**
 * MyBatisで{@link LocalDate}を扱うためのハンドラー.
 */
@MappedTypes(LocalDate.class)
public class LocalDateTypeHandler
    extends BaseTypeHandler<java.time.LocalDate> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, java.time.LocalDate parameter,
      JdbcType jdbcType) throws SQLException {
    ps.setDate(i, Date.valueOf(parameter));
  }

  @Override
  public java.time.LocalDate getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    Date date = rs.getDate(columnName);
    if (date == null) {
      return null;
    } else {
      return date.toLocalDate();
    }
  }

  @Override
  public java.time.LocalDate getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    Date date = rs.getDate(columnIndex);
    if (date == null) {
      return null;
    } else {
      return date.toLocalDate();
    }
  }

  @Override
  public java.time.LocalDate getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    Date date = cs.getDate(columnIndex);
    if (date == null) {
      return null;
    } else {
      return date.toLocalDate();
    }
  }
}
```


2.Mybatis のconfig.xml に追加します。

```config.xml
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="useColumnLabel" value="true"/>
    </settings>
    <!-- typeHandler を追加 -->
    <typeHandlers>
        <typeHandler handler="com.example.typeHandler.LocalDateTypeHandler"/>
    </typeHandlers>
</configuration>
```

同様に`LocalDateTime`、`LocalTime`のTypeHandler を作成し、登録する必要がありますが、ここでは割愛します。

## Thymeleaf
Thymeleaf については、thymeleaf-extras-java8time の追加が必要になります。
`#dates` はJava8 日時APIには対応していないので、`#temporals` を使用します。


1.pom.xml にthymeleaf-extras-java8time を追加。

```xml:pom.xml
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-java8time</artifactId>
			<version>2.1.0.RELEASE</version>
		</dependency>
```

2.`Java8TimeDialect` クラスをBean登録する。

```java:ThymeleafConfiguration
  @Bean
  public Java8TimeDialect java8TimeDialect() {
    return new Java8TimeDialect();
```

テンプレートでは`#temporals` を使用します。

```html
            <label name="testDate" th:text="${#temporals.format(testDate, 'yyyy-MM-dd')}" />
```

## まとめ
どうでしたか？
いろいろ設定が必要なのが現状です。
新しくJava8 日時API が追加されましたが、まだまだ使い勝手がよろしくないかな、といった印象を私は感じました。

早く特別な設定なしでJava8 日時API が使用できるようになるといいですね！