Help us understand the problem. What is going on with this article?

Spring Boot 1.5→2.1 に移行したときのお話

はじめに

Spring Boot 1系が2019年8月1日にEOLを迎えるということで、今更ながらSpring Bootのバージョンを2系に更新しました(1.5.22.1.5)。
そのときにやったことをメモしておきます。

主な構成

  • Java 8
  • Gradle 4.10.3
  • Web、バッチ両方ありの巨大なマルチプロジェクト
    • Springアプリケーション5個
    • プレーンなJavaアプリケーション1個
    • ライブラリ系のモジュール6個
  • MySQL
  • Flyway
  • JOOQ
  • Thymeleaf

やったこと

情報収集

↓の公式の移行ガイドに基本的な移行方法や注意点が書かれています。
Spring Boot 2.0 Migration Guide

公式の移行ガイドを読みながら作業し、不明な点や躓いた点はググって解決していきました。
また、メジャーバージョンが変わる主要ライブラリについては、リリースノートを見て破壊的変更がないかチェックしました。

1. まずはspring-boot-properties-migratorをdependenciesに追加(※移行が終わったら外す)

Spring Boot 2でapplication.properties/application.ymlで定義するプロパティのキーや設定内容が変わったものがあります。
spring-boot-properties-migratorをdependenciesに足しておくと、アプリケーション起動時に、設定内容が古いままになっているプロパティがある場合に警告ログを出してくれます。また、一時的に古いプロパティの書き方のままでも動作するようにしてくれます。

dependenciesに↓の行を追加します。(※移行が終わったら外します)

build.gradle
runtime "org.springframework.boot:spring-boot-properties-migrator"

2. GradleのDependency Managementプラグインを追加

1系ではSpring Bootプラグインの中にDependency Managementプラグインが含まれていましたが、2系からはDependency Managementプラグインは別になったようです。
build.gradleにDependency Managementプラグインを追加します。

  apply plugin: 'org.springframework.boot'
+ apply plugin: 'io.spring.dependency-management' // <-- コレを追加

3. Spring Bootのバージョンを上げる

  etx {
-   springBootVersion = '1.5.2.RELEASE'
+   springBootVersion = '2.1.5.RELEASE'
  }

Sprin Bootのバージョンを上げると、いろいろのライブラリのバージョンも自動的に上がります。

今回の主な変更

  • Spring (4→5)
  • Thymeleaf (2→3)
  • MySQLのJDBCドライバー (5.1→8.0)
  • Flyway (4→5)
  • JOOQ (3.9→3.11)

また、RDBのコネクションプールのライブラリがTomcat JDBCからHikariCPに変わりました。

※ 各ライブラリの具体的なバージョン番号はspring-boot-dependenciesのPOMファイルに載っています

4. Spring Bootの設定

4.1. Gradleの設定

bootRepackageというタスクがなくなり、bootJarという名前のタスクになりました

build.gradle
bootJar {
  mainClassName = 'foo.bar.App'
  launchScript()                // <-- bootRapackageで`executable = true`を設定していた場合、これがその代わりになる
}

4.2. プロパティをバインドするときのキー名を修正

@ConfigurationProperties@ValueでSpring BootのプロパティをJavaにバインドしている場合は、プロパティ名の書き方を"Canonical"なものに統一する必要があります。
詳しくはSpring Boot 2.0 Canonical Propertiesを参照してください。

基本的には全部小文字にし、「-」と「_」は削除すればOKでした

- @ConfigurationProperties(prefix = "foo-bar.apiKey")
+ @ConfigurationProperties(prefix = "foobar.apikey")

5. Spring Web MVC

5.1. WebMvcConfigurerAdapter→WebMvcConfigurer

WebMvcConfigurerAdapterクラスが廃止になり、WebMvcConfigurerインターフェースに変更されました

- public class WebMvcConfig extends WebMvcConfigurerAdapter {
+ public class WebMvcConfig implements WebMvcConfigurer {

5.2. Controllerで拡張子ありのURLの自動マッピングができなくなった

@GetMapping("/users")
public List<Users> listUsers() {

のようなエンドポイントを用意し、これに対してフロントエンドからGET /users.jsonのようなパスでアクセスしていましたが、これができなくなりました。
普通にGET /usersでアクセスすればOKなので、.jsonを全部消しました。

6. MySQL

5.18.0になります

6.1. MySQLのドライバーのクラス名を変更

com.mysql.jdbc.Driverとなっている箇所を探してcom.mysql.cj.jdbc.Driverに置換しました。

7. JOOQ

3.93.11になります。
JOOQのバージョンが新しくなったことに伴い、何箇所か修正しました。
The jOOQ Release Note HistoryBreaking changesは一通り読んでおくといいと思います。

7.1. JOOQのGradleプラグインのバージョンを上げる

JOOQ3.11.x以降は、gradle-jooq-plugin3.xを使う必要があります

 plugins {
-  id 'nu.studer.jooq' version '2.0.11'
+  id 'nu.studer.jooq' version '3.0.3'
 }

7.2. JOOQのコード生成のGradle設定を修正

APIのパッケージ名が変わっていたので修正

  mainDb(sourceSets.main) {
    jdbc {
      ...
    }
    generator {
-     name = 'org.jooq.util.DefaultGenerator'
+     name = 'org.jooq.codegen.DefaultGenerator'
      ...
      database {
-       name = 'org.jooq.util.mysql.MySQLDatabase'
+       name = 'org.jooq.meta.mysql.MySQLDatabase'
        ...

7.3. JOOQの生成をし直す

コードを再生成します

7.4. Cursor#fetchOne()が非推奨になったので修正

 while (cursor.hasNext()) 
-  Record record = cursor.fetchOne();
+  Record record = cursor.fetchNext();

8. Flyway

※3系から5系に移行する場合は、一旦4系にする必要があるそうです
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#flyway

8.1. プロパティ名変更

application.properties / application.ymlのプロパティ名が、flyway.*spring.flyway.*になりました

application.yml.diff
- flyway:
-   enabled: true
+ spring:
+   flyway:
+     enabled: true

8.2. マイグレーション実行履歴管理テーブルの名前を設定

Flyway 3系, 4系ではschema_versionという名前のテーブルでFlywayの実行履歴が管理されていましたが、5系ではflyway_schema_historyという名前に変わっています。
schema_versionを引き続き使用するためにはapplication.properties / application.ymlspring.flyway.tableというプロパティを設定します。

application.yml
spring:
  flyway:
    table: schema_version

また、GradleでFlywayプラグインを使用している場合はそちらにも設定しておきます

build.gradle
flyway {
  ...
  table = 'schema_version'
}

9. Thymeleaf

9.1. 依存ライブラリの修正

- implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4'
+ implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' // <--- Spring SecurityのDialectのバージョンアップ

9.2. テンプレートレイアウトの記述方法を修正

Thymeleaf3からレイアウトの記法が変わりました。
参考:

レイアウト
layout/default.html.diff
  <!DOCTYPE html>
  <html lang="ja"
        xmlns:th="http://www.thymeleaf.org"
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
  <head>
    <meta charset="utf-8"/>
    <!-- 「{各ページのタイトル} | アプリ名」のようなtitleになる -->
-   <title layout:title-pattern="$CONTENT_TITLE | $DECORATOR_TITLE">アプリ名</title>
+   <title layout:title-pattern="$CONTENT_TITLE | $LAYOUT_TITLE">アプリ名</title>
    ...
  </head>
  <body>
    <!-- 共通のHTMLを埋め込む -->
-   <div layout:replace="common/header::partial"></div>
+   <div layout:replace="~{common/header::partial}"></div>

    <!-- 各ページのコンテンツをここに展開 -->
    <div layout:fragment="content"></div>
  </bod>
  </html>
個別ページ
sample/index.html.diff
  <!DOCTYPE html>
  <html
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.springframework.org/schema/mvc"
-     layout:decorator="layout/default">
+     layout:decorate="~{layout/default}">
  <head>
    <title>各ページのタイトル</title>
  </head>
  <body>
    <!-- 各ページのコンテンツ -->
    <div layout:fragment="content">
      Hello World
    </div>
  </body>
  </html>

10. コネクションプール

Tomcat JDBCからHikariCPに変更なったので、設定内容を修正します。

プロパティ名は
spring.datasource.tomcat.*spring.datasource.hikari.*
になります。

HikariCPで設定可能な項目はcom.zaxxer.hikari.HikariConfigに定義されています。

トラブルシューティング

Jar生成時にフロントエンドのビルド内容が含まれなくなってしまった

もともとGradleでフロントエンドのビルドを行うタスクを定義し、jar作成時に実行されるように設定していました。

build.gradle
jar.dependsOn compileFrontend

が、Spring Boot 2系からはbootJarタスクでjarを生成するようになったので↓のように変更しました

build.gradle
bootJar.dependsOn compileFrontend

DBUnitがコケる(MySQL)

org.dbunit.database.AmbiguousTableNameException: ACCOUNTS

複数のデータベース間で同じ名前のテーブルが存在するときにエラーが発生するようです。
JDBCのURLにnullCatalogMeansCurrent=trueというクエリパラメータを追加したら直りました。
(MySQLのJDBCドライバーのデフォルトの挙動が変わったようです)

JOOQでTINYINT(1)のフィールドに対してSELECTしたときにコケる

java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.Byte

JOOQを利用するときの方針としてBIT(1)をBoolean、TINYINT(1)をByteにマッピングしていました。
が、Spring Boot 2にアップデート後、TINYINT(1)がBooleanにマッピングされるようになってしまいClassCastExceptionが発生していました。

JDBCのURLにtinyInt1isBit=falseというクエリパラメータを追加したら直りました。
MySQLのJDBCドライバーは内部的にTINYINT(1)BIT(1)として扱うらしく、Booleanになってしまったようです。

WEBのドメイン名に「_」が入っている場合に、IllegalArgumentException

java.lang.IllegalArgumentException: The character [_] is never valid in a domain name.

Tomcatのバージョンが上がり、_を含むドメイン名を受け付けなくなったようです。
(ドメイン名に_が含まれるのはRFCの仕様としておかしいらしい。)
→サブドメインに_が入っていたので、サブドメイン名を変更して対応しました。

参考: https://stackoverflow.com/questions/53504857/the-character-is-never-valid-in-a-domain-name

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away