はじめに
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
今回マイグレーションしたアプリケーションは、次のようなライブラリに依存しています。
<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に変えることに。
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
</parent>
application.ymlのプロパティ変更
context-pathの階層が変わりました。元のままでも動作するようですが、変えておいた方が無難です。
server:
context-path: /sample
server:
servlet:
context-path: /sample
Adapterクラスの廃止
Spring Boot 2.0はJava 8対応となったため、インタフェースを空実装したAdapterクラスは廃止され、代わりにインタフェース自体にメソッドのデフォルト実装が追加されています。
そのため、Adapterのextendsではなく、直接インタフェースをimplementsするように変更となります。
public class WebMvcConfig extends WebMvcConfigurerAdapter {
public class WebMvcConfig implements WebMvcConfigurer {
Auto-configurationのパッケージ変更
プロファイルによってセキュリティ設定を切り替えるために、セキュリティのAuto-configurationであるSecurityAutoConfigurationを除外していましたが、このSecurityAutoConfigurationのパッケージが変更となっています。
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
securityパッケージ配下のさらにservletパッケージの中に移動したようです。
PasswordEncoder関連クラスの構成変更
内部キャッシュ用にShaPasswordEncoderを使っていましたが、クラス自体がなくなりました。
PasswordEncoder関連のクラス構成が見直されています。それに合わせて、脆弱なアルゴリズムを使ったShaPasswordEncoderクラスやMd5PasswordEncoderクラスは姿を消しています(アルゴリズム自体は残っているようですが、非推奨とのこと)。
Spring Boot 2.0からは、PasswordEncoderFactoriesを使ってPasswordEncoderを生成することになります。
MessageDigestPasswordEncoder encoder = new ShaPasswordEncoder(256);
return encoder.encodePassword(password);
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return encoder.encode(password);
メソッドも、encodePassword()からencode()に変わっています。
Repositoryのメソッド名称/型変更
今回の修正で最も大変だったのがコレです(数が多かったので・・・)。
CrudRepositoryのfindOne()メソッドがfindById()に名称変更となり、さらに戻り値がEntity型ではなくOptional型になりました。
Employee employee = employeeRepository.findOne(employeeId);
Optional<Employee> employee = employeeRepository.findById(employeeId);
Repositoryのページングの生成方法変更
PageRequestのnewが非推奨になりました。代わりにof()を使います。
employeeRepository.findAll(specifications,
new PageRequest(page, size, new Sort(
Sort.Direction.fromString(sortDirection), sortColumn)));
employeeRepository.findAll(specifications,
PageRequest.of(page, size, new Sort(
Sort.Direction.fromString(sortDirection), sortColumn)));
PageRequestをof()に変えるなら、Sortもof()に変えた方がスタイルが統一されてよいですね。
employeeRepository.findAll(specifications,
PageRequest.of(page, size, Sort.of(
Sort.Direction.fromString(sortDirection), sortColumn)));
Specificationsの非推奨化
Java 8になり、Specificationインタフェースにデフォルト実装が追加されたため、Specificationインタフェースを実装したSpecificationsクラスの直接利用は非推奨となりました。
(Specificationインタフェースのデフォルト実装は、Specificationsクラスを使っていますが。)
employeeRepository.findAll(
Specifications.where((root, query, cb) -> cb.equal(property, value)));
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を使います。
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();
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になりました。
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 {
// ・・・
}
// ・・・
}
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")
のように、チェインケースをキャメルケースで参照してバインドが可能でした。
Spring Boot 2.0では、チェックが厳しくなり、上の状態だとエラーになってしまいます。(本来はエラーになるのが当然ですが。。)
ちゃんと定義に合わせてチェインケースで書きましょう。。
@ConfigurationProperties(prefix = "app.function.message-type")
まとめ
今回のマイグレーションで遭遇しなかった変更点もたくさんあります。
公式サイトに Spring Boot 2.0 Migration Guide がありますので、こちらも参考にしてみてください。