Java
migration
spring
spring-boot

SpringBoot1.5からSpringBoot2.0にマイグレーションしたときの変更点

はじめに

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

余談

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

SpringBootの修正箇所

Maven

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

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を使用していたのですが、EOLが2019年9月となっており、「spring-boot-starter-parentを使用するか、spring-boot-dependencies bomをインポートするのを推奨」と書かれていました。
推奨に従い、Spring IO platformからspring-boot-starter-parentに変えることに。

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>
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の階層が変わりました。元のままでも動作するようですが、変えておいた方が無難です。

SpringBoot1.5
server:
  context-path: /sample
SpringBoot2.0
server:
  servlet:
    context-path: /sample

Adapterクラスの廃止

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

SpringBoot1.5
public class WebMvcConfig extends WebMvcConfigurerAdapter {
SpringBoot2.0
public class WebMvcConfig implements WebMvcConfigurer {

Auto-configurationのパッケージ変更

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

SpringBoot1.5
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;

@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
SpringBoot2.0
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)

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

PasswordEncoder関連クラスの構成変更

内部キャッシュ用にShaPasswordEncoderを使っていましたが、クラス自体がなくなりました。
PasswordEncoder関連のクラス構成が見直されています。それに合わせて、脆弱なアルゴリズムを使ったShaPasswordEncoderクラスやMd5PasswordEncoderクラスは姿を消しています(アルゴリズム自体は残っているようですが、非推奨とのこと)。

SpringBoot2.0からは、PasswordEncoderFactoriesを使ってPasswordEncoderを生成することになります。

SpringBoot1.5
MessageDigestPasswordEncoder encoder = new ShaPasswordEncoder(256);
return encoder.encodePassword(password);
SpringBoot2.0
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return encoder.encode(password);

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

Repositoryのメソッド名称/型変更

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

SpringBoot1.5
Employee employee = employeeRepository.findOne(employeeId);
SpringBoot2.0
Optional<Employee> employee = employeeRepository.findById(employeeId);

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

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

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

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

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

Specificationsの非推奨化

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

SpringBoot1.5
employeeRepository.findAll(
        Specifications.where((root, query, cb) -> cb.equal(property, value)));
SpringBoot2.0
employeeRepository.findAll(
        Specification.where((root, query, cb) -> cb.equal(property, value)));

Hibernateの変更箇所

SpringBootを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を使います。

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();
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の実装クラスがありました。
SpringBoot 2.0(Hibernate 5.2.17)では、nullSafeGet/nullSafeSetの引数のSessionImplementorクラスがなくなっており、代わりにSharedSessionContractImplementorになりました。

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 {
        // ・・・
    }

    // ・・・
}
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に

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

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

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

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

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

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

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

まとめ

今回のマイグレーションで遭遇しなかった変更点もたくさんあります。
公式サイトに Spring Boot 2.0 Migration Guide がありますので、こちらも参考にしてみてください。