Spring Boot 1.5からSpring Boot 2.0にマイグレーションしたときの変更点

# はじめに

Spring Boot 1.5のアプリケーション（約7KL、SPAのサーバ部分）をSpring Boot 2.0にマイグレーションしてみたときの修正ポイントを書いておきます。
クラス・パッケージが変わったり、Deprecatedになったりと、結構な量の修正が必要でした。

## 余談

最初はSpring Bootを2.0にするつもりはなく、Elasticsearch 6にアクセスしたかっただけなのですが、Elasticsearch 6にアクセスするためにはspring-boot-starter-data-elasticsearchを2.0台に上げなければならず、仕方なしにSpring Boot自体を2.0に上げることにしました。

# Spring Bootの修正箇所

## Maven

今回マイグレーションしたアプリケーションは、次のようなライブラリに依存しています。

```xml:pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.ldap</groupId>
  <artifactId>spring-ldap-core</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.ldap</groupId>
  <artifactId>spring-ldap-core-tiger</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-ldap</artifactId>
</dependency>
```

もともとは[Spring IO platform](http://platform.spring.io/platform/)を使用していたのですが、EOLが2019年9月となっており、「spring-boot-starter-parentを使用するか、spring-boot-dependencies bomをインポートするのを推奨」と書かれていました。
推奨に従い、Spring IO platformからspring-boot-starter-parentに変えることに。

```xml:SpringBoot1.5
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.spring.platform</groupId>
      <artifactId>platform-bom</artifactId>
      <version>Brussels-SR3</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
```

```xml:SpringBoot2.0
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.2.RELEASE</version>
</parent>
```

## application.ymlのプロパティ変更

context-pathの階層が変わりました。元のままでも動作するようですが、変えておいた方が無難です。

```yaml:SpringBoot1.5
server:
  context-path: /sample
```

```yaml:SpringBoot2.0
server:
  servlet:
    context-path: /sample
```

## Adapterクラスの廃止

Spring Boot 2.0はJava 8対応となったため、インタフェースを空実装したAdapterクラスは廃止され、代わりにインタフェース自体にメソッドのデフォルト実装が追加されています。
そのため、Adapterのextendsではなく、直接インタフェースをimplementsするように変更となります。

```java:SpringBoot1.5
public class WebMvcConfig extends WebMvcConfigurerAdapter {
```

```java:SpringBoot2.0
public class WebMvcConfig implements WebMvcConfigurer {
```

## Auto-configurationのパッケージ変更

プロファイルによってセキュリティ設定を切り替えるために、セキュリティのAuto-configurationであるSecurityAutoConfigurationを除外していましたが、このSecurityAutoConfigurationのパッケージが変更となっています。

```java:SpringBoot1.5
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;

@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
```

```java:SpringBoot2.0
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
```

securityパッケージ配下のさらにservletパッケージの中に移動したようです。

## PasswordEncoder関連クラスの構成変更

内部キャッシュ用にShaPasswordEncoderを使っていましたが、クラス自体がなくなりました。
PasswordEncoder関連のクラス構成が見直されています。それに合わせて、脆弱なアルゴリズムを使ったShaPasswordEncoderクラスやMd5PasswordEncoderクラスは姿を消しています（アルゴリズム自体は残っているようですが、[非推奨とのこと](https://info.michael-simons.eu/2018/01/13/spring-security-5-new-password-storage-format/)）。

Spring Boot 2.0からは、PasswordEncoderFactoriesを使ってPasswordEncoderを生成することになります。

```java:SpringBoot1.5
MessageDigestPasswordEncoder encoder = new ShaPasswordEncoder(256);
return encoder.encodePassword(password);
```

```java:SpringBoot2.0
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return encoder.encode(password);
```

メソッドも、encodePassword()からencode()に変わっています。

## Repositoryのメソッド名称／型変更

今回の修正で最も大変だったのがコレです（数が多かったので・・・）。
CrudRepositoryのfindOne()メソッドがfindById()に名称変更となり、さらに戻り値がEntity型ではなくOptional型になりました。

```java:SpringBoot1.5
Employee employee = employeeRepository.findOne(employeeId);
```

```java:SpringBoot2.0
Optional<Employee> employee = employeeRepository.findById(employeeId);
```

## Repositoryのページングの生成方法変更

PageRequestのnewが非推奨になりました。代わりにof()を使います。

```java:SpringBoot1.5
employeeRepository.findAll(specifications,
        new PageRequest(page, size, new Sort(
                Sort.Direction.fromString(sortDirection), sortColumn)));
```

```java:SpringBoot2.0
employeeRepository.findAll(specifications,
        PageRequest.of(page, size, new Sort(
                Sort.Direction.fromString(sortDirection), sortColumn)));
```

PageRequestをof()に変えるなら、Sortもof()に変えた方がスタイルが統一されてよいですね。

```java:SpringBoot2.0
employeeRepository.findAll(specifications,
        PageRequest.of(page, size, Sort.of(
                Sort.Direction.fromString(sortDirection), sortColumn)));
```

## Specificationsの非推奨化

Java 8になり、Specificationインタフェースにデフォルト実装が追加されたため、Specificationインタフェースを実装したSpecificationsクラスの直接利用は非推奨となりました。
（Specificationインタフェースのデフォルト実装は、Specificationsクラスを使っていますが。）

```java:SpringBoot1.5
employeeRepository.findAll(
        Specifications.where((root, query, cb) -> cb.equal(property, value)));
```

```java:SpringBoot2.0
employeeRepository.findAll(
        Specification.where((root, query, cb) -> cb.equal(property, value)));
```

# Hibernateの変更箇所

Spring Bootを2.0にしたことに伴いHibernateのバージョンが5.2.17に上がったことによる変更もありました。

## Hibernateのパッケージ変更

HibernateのNativeQueryを使って、明示的にNULLを指定したいときにExplicitParameterInfoクラスを使っていましたが、このクラスのパッケージが `org.hibernate.jpa.criteria.compile.ExplicitParameterInfo` から `org.hibernate.query.criteria.internal.compile.ExplicitParameterInfo` に変わりました。

**2018/07/12追記・更新**
PostgreSQL 10の場合は少なくとも、NULLを指定する際にExplicitParameterInfoが指定できなくなりました。
ExplicitParameterInfoを使うと「 `org.postgresql.util.PSQLException: ERROR: 演算子が存在しません: text = bytea` 」のようなエラーが発生します。
そのため、NULLの型を指定したい場合は、ExplicitParameterInfoの代わりにTypedParameterValueを使います。

```java:SpringBoot1.5
import org.hibernate.jpa.criteria.compile.ExplicitParameterInfo;

// 中略

Query query = entityManager.createNativeQuery(query);
query.setParameter(new ExplicitParameterInfo<>("employeeName", null, String.class), null);
List<Employee> employeeList = query.getResultList();
```

```java:SpringBoot2.0
import org.hibernate.jpa.TypedParameterValue;
import org.hibernate.type.StringType;

// 中略

Query query = entityManager.createNativeQuery(query);
query.setParameter("employeeName", new TypedParameterValue(new StringType(), null));
List<Employee> employeeList = query.getResultList();
```

## UserTypeのメソッド引数変更

今回のアプリケーションには、PostgreSQLのJSONBカラムとJavaのStringを相互変換する、UserTypeの実装クラスがありました。
Spring Boot 2.0（Hibernate 5.2.17）では、nullSafeGet/nullSafeSetの引数のSessionImplementorクラスがなくなっており、代わりにSharedSessionContractImplementorになりました。

```java:SpringBoot1.5
public class PostgresJsonType implements UserType {
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names,
            SessionImplementor session, Object owner)
            throws HibernateException, SQLException {
        // ・・・
    }

    public void nullSafeSet(PreparedStatement st, Object value,
            int index, SessionImplementor session)
            throws HibernateException, SQLException {
        // ・・・
    }

    // ・・・
}
```

```java:SpringBoot2.0
public class PostgresJsonType implements UserType {
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names,
            SharedSessionContractImplementor session, Object owner)
            throws HibernateException, SQLException {
        // ・・・
    }

    public void nullSafeSet(PreparedStatement st, Object value,
            int index, SharedSessionContractImplementor session)
            throws HibernateException, SQLException {
        // ・・・
    }

    // ・・・
}
```

# その他

これは元から実装が悪かったのですが、application.ymlに

```yaml
app:
  function:
    message-type:
      AAA: xxx
      BBB: yyy
```

という定義があり、ConfigurationPropertiesで値をバインドする際に

```java
@ConfigurationProperties(prefix = "app.function.messageType")
```

のように、チェインケースをキャメルケースで参照してバインドが可能でした。

Spring Boot 2.0では、チェックが厳しくなり、上の状態だとエラーになってしまいます。（本来はエラーになるのが当然ですが。。）

ちゃんと定義に合わせてチェインケースで書きましょう。。

```java
@ConfigurationProperties(prefix = "app.function.message-type")
```

# まとめ

今回のマイグレーションで遭遇しなかった変更点もたくさんあります。
公式サイトに [Spring Boot 2.0 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide) がありますので、こちらも参考にしてみてください。