KINTO Tech Blog
Development

Spring Bootを2系から3系へバージョンアップしました。

Daisuke Takehana
Daisuke Takehana
Cover Image for Spring Bootを2系から3系へバージョンアップしました。

Spring Bootを2系から3系へバージョンアップしました。

自己紹介

こんにちは。プラットフォーム開発部/共通サービス開発グループ[1][2][3][4]/決済プラットフォームチームの竹花です。
今回は、決済プラットフォームのAPI・バッチに使用しているSpring Bootのバージョンアップを行った話について書きたいと思います。

解決したい課題・実現したいこと

  • 当時使用していたSpring Bootが2系であったため、サポート期間等を鑑み3系にバージョンアップしたい。
  • 伴って使用しているライブラリのバージョンアップも実施
ライブラリ 移行前(2系) 移行後(3系)
Java 17 変更なし
MySQL 5.7 8.0
Spring Boot 2.5.12 3.1.0
Spring Boot Security 2.5.12 3.1.0
Spring Boot Data JPA 2.5.12 3.1.0
hibernate Types 2.21.1 3.5.0
MyBatis Spring Boot 2.2.0 3.0.2
Spring Batch 4.3 5.0
Spring Boot Batch 2.5.2 3.0.11
Spring Boot Cloud AWS 2.4.4 3.0.1

試行錯誤した内容・工夫したこと

適用方法

まずは公式のmigrationガイドを参考に、既存コードのまま影響が少ないライブラリのupdate、deprecated解消等を実施。
その後3.1.0に上げて、ひたすら地道に修正 → build、およびテスト → 調整を繰り返しました。

Spring Boot 3.1 Release Notes
Spring Boot 3.0 Migration Guide
Spring Batch 5.0 Migration Guide

javaxJakarta

多くのファイルに影響を与えるjavaxJakartaパッケージへの変更がありました。
パッケージルート以降の名称は変わりなかったので、機械的に置換しました。

DBアクセス周り

MySQL-Connector-Java

移行されたとのことで、mysql-connector-jへ変更しました。
Maven Repository

MySQLDialect

org.hibernate.dialect.MySQLDialectにすることでMySQLのversion違いを吸収してくれるようになりました。
image1.png

Hibernate-Types

バージョンアップに伴い、JPA Entityで使用していたJson型の設定方法が変わりました。
image2.png

ID generateをIDENTITYに変更

Spring DATA JPAで自動採番の方法が変わったようで、 AUTOのままではXXX_seqというテーブルを必要とするようになりました。
我々のシステムではMySQLのAuto Incrementを使っていたためJPAで採番機能を利用しないよう対応しました。
image3.png

Spring Batch

Metaテーブルの変更

Spring Batchの管理テーブルの構造が変更されました。
Migration Guidを参考に、
/org/springframework/batch/core/migration/5.0/migration-mysql.sql
を使って既存テーブルを更新しました。
ですが、ALTER TABLEを実行しただけでは既存データにより動作時にエラーとなってしまったため、今後の運用に影響しないことを確認してデータを初期化することにしました。

Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: LONG
	at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao$2.processRow(JdbcJobExecutionDao.java:468)
...

(BATCH_JOB_EXECUTION_PARAMSのPARAMETER_TYPEに'LONG'という値が入っていました)

データの初期化は以下のSQLで行いました。

TRUNCATE TABLE BATCH_STEP_EXECUTION_CONTEXT;
TRUNCATE TABLE BATCH_STEP_EXECUTION_SEQ;
TRUNCATE TABLE BATCH_JOB_SEQ;
TRUNCATE TABLE BATCH_JOB_EXECUTION_SEQ;
TRUNCATE TABLE BATCH_JOB_EXECUTION_PARAMS;
TRUNCATE TABLE BATCH_JOB_EXECUTION_CONTEXT;

SET foreign_key_checks = 0;
TRUNCATE TABLE BATCH_JOB_EXECUTION;
TRUNCATE TABLE BATCH_JOB_INSTANCE;
TRUNCATE TABLE BATCH_STEP_EXECUTION;
SET foreign_key_checks = 1;

INSERT INTO BATCH_STEP_EXECUTION_SEQ VALUES(0, '0');
INSERT INTO BATCH_JOB_EXECUTION_SEQ VALUES(0, '0');
INSERT INTO BATCH_JOB_SEQ values(0, '0');

BasicBatchConfigurerが使えなくなった

DefaultBatchConfigurationを使う方法に変更しました。

StepBuilderFactory、JobBuilderFactoryが非推奨となった

new StepBuilder()でJobRepositoryとTransactionManagerを渡すように変更しました。
image4.png

ItemWriterの引数の型が変わった

Listだったものがorg.springframework.batch.item.Chunkに変わったので、write処理を修正しました。

修正前 修正後
<pre>ItemWriter<Dto> write() {
return items -> {
...
items.stream()
.flatMap(dto -> dto.getDatas().stream())
.forEach(repository::update);
...</pre>
<pre>ItemWriter<Dto> write() {
return items -> {
...
items.getItems().stream()
.flatMap(dto -> dto.getDatas().stream())
.forEach(repository::update);
...</pre>

@EnableBatchProcessingの挙動が変わった

動作確認時に、chunkモデルのバッチでprocess処理がスキップ?されるような現象が発生しました。
@EnableBatchProcessing挙動が変わったことによる影響で、こちらのアノーテーションを外すことで解消しました。

Spring Cloud AWS

ライブラリの変更

本システムではAWSサービスを多く利用しており、連携にSpring Cloud AWSを利用していました。
バージョンアップに伴い、io.awspring.cloud:spring-cloud-starter-awsio.awspring.cloud:spring-cloud-aws-starterに変更し(紛らわしい...)、
com.amazonaws:aws-java-sdksoftware.amazon.awssdkへ置き換え、動作するよう修正を行いました。
Spring Cloud AWS

SES

SESに関してAmazonSimpleEmailServiceが使えなくなったため、JavaMailSenderを使う実装へ変更しました。
使用するJavaMailSenderは、SESのAuto Configurationで構築されたものをDIして利用します。

SQS

SQSへの送信等、リクエスト用のオブジェクトをビルダーパターンで構築する形になったので、これに合わせて修正しました。
また、SQSListenerで使用していた@NotificationMessageが無くなったたため、SqsListenerConfigurerを作成し、MessageConverterを用意することで対応しました。
image5.png

@Bean
public SqsListenerConfigurer configurer(ObjectMapper objectMapper) {
  return registrar ->
      registrar.manageMessageConverters(
          list ->
              list.addAll(
                  0,
                  List.of(
                      new SQSEventModelMessageConverter(
                          objectMapper, ReciveEventModel.class),
...
}

@RequiredArgsConstructor
private static class SQSEventModelMessageConverter implements MessageConverter {

  private static final String SQS_EVENT_FILED_MESSAGE = "Message";

  private final ObjectMapper objectMapper;

  private final Class<?> modelClass;

  @Override
  public Object fromMessage(Message<?> message, Class<?> targetClass) {
    if (modelClass != targetClass) {
      return null;
    }

    try {
      val payload =
          objectMapper
              .readTree(message.getPayload().toString())
              .get(SQS_EVENT_FILED_MESSAGE)
              .asText();
      return objectMapper.readValue(payload, targetClass);
    } catch (IOException ex) {
      throw new MessageConversionException(
          message, " Could not read JSON: " + ex.getMessage(), ex);
    }
...
}

S3

S3へのアップロードについて、TransferManagerS3TransferManagerに変更、署名付きURL発行の実装も修正が必要でした。
image6.png

SNS

SNS送信に関してDefaultTopicArnResolverのままではsns:CreateTopic権限が必須になっていました。
TopicsListingTopicArnResolverを使うようにして、CreateTopic権限不要で動作するよう対応しました。

@ConditionalOnProperty("spring.cloud.aws.sns.enabled")
@Configuration
public class SNSConfig {
  @Bean
  public TopicArnResolver topicArnResolver(SnsClient snsClient) {
    return new TopicsListingTopicArnResolver(snsClient);
  }
}

API周り

WebSecurityConfigurerAdapter参照不可

リンクを参考にSecurityFilterChainを使用する方法に変更しました。
spring-security

URLパスの厳格化

Trailing Slash(末尾スラッシュ付き)パスが厳格に区別されるようになりました。
本システムは内部の別システムとの連携があったため@RequestMappingにパスを追加し、 対向システムとの調整後追加したパスを除去する流れで対応しました。

追加前 追加後
<pre>...
@RequestMapping(
method = RequestMethod.GET,
value = {"/api/payments/{id}"},
...
</pre>
<pre>...
@RequestMapping(
method = RequestMethod.GET,
value = {"/api/payments/{id}", "/api/payments/{id}/"},
...
</pre>

プロパティ名の変更(application.yml)

jpa、redis、spring-cloud-aws系などのプロパティ名が変更されました。
こちらは公式情報を参考に適宜調整しました。

デプロイ

ECSデプロイで404が発生

なんとかECSへデプロイできるところまで進み、アプリケーションログで起動が確認できたのですが、APIにアクセスすると404に。
確認したところ、ECSのデプロイでHealth Checkが失敗していました。
弊社クラウドプラットフォームエンジニアの方々にも協力を頂き、テレメトリデータ収集に使用していたaws-opentelemetry-agentのversionが古かったことを突き止めました。
image7.png

1.23.0以降のバージョンのjarに変更することで、正常にデプロイされAPIの疎通が確認できるようになりました。
AWSが提供するOpenTelemetryJava

結果・プラスアルファの知見や応用案、次へのトライなど

様々な要件に対応するためSpring Bootの一般的な実装パターンとなっていない箇所もあるなど、
migration guideだけですんなりと移行できるような構成でなかったところもありますが、
Try & Errorの繰り返しでなんとかリリースすることができました。
地道な積み重ねの作業・レビューに付き合って頂いたチームの皆さんに感謝しています。
今後は下記のような残課題を引き続き対応しながら、Spring Boot3で強化された機能等も活用していきたいと思います。

Swagger-UI

こちらはバージョンアップを後回しにしました。
使用していたspring-foxがまだSpring boot3系に対応していないため、springdoc-openapiに変更することも検討しています。

Spring Batch + MySQL 8.0 + DbUnit

一部条件に該当するテストがエラーになってしまいました。
Spring BatchのTransaction管理(metaテーブル操作)が関連していそうなのですが、修正方法について調査中です。

本記事の事例を通して学んだことまとめ

  • Spring Bootのバージョンアップはmigration guideを参考にしながら、地道にbuild・テスト繰り返すことで実施できた。
  • 今回のバージョンアップは影響範囲が大きかったが、テストを用意していたことで修正すべきところが明確になり、効率よく対応できた。
  • @EnableBatchProcessingの変更のように動かしてみて問題が見つかるケースもあったため、動作確認も必須。
  • JavaEE関連がJakartaに変わったことで、Spring Bootだけでなく他のライブラリの更新も必要だった。
  • Security関連がより強固になった(Trailing Slashの厳格化やIgnore PathでもAuthFilterを通るようになった等)。
  • 依存ライブラリ別で更新内容に差があり、特にSpring Cloud AWSの変更は大きく感じた。
  • もう少しこまめにライブラリ別のバージョンアップを実施していれば、修正範囲を小さくできたかもしれない。

本記事をお読みいただきありがとうございました。
同様にバージョンアップを検討されている方の参考になれば幸いです。

脚注
  1. 共通サービス開発グループメンバーによる投稿 1
    [ グローバル展開も視野に入れた決済プラットフォームにドメイン駆動設計(DDD)を取り入れた ] ↩︎

  2. 共通サービス開発グループメンバーによる投稿 2
    [入社 1 年未満メンバーだけのチームによる新システム開発をリモートモブプログラミングで成功させた話] ↩︎

  3. 共通サービス開発グループメンバーによる投稿 3
    [JIRA と GitHub Actions を活用した複数環境へのデプロイトレーサビリティ向上の取り組み] ↩︎

  4. 共通サービス開発グループメンバーによる投稿 4
    [ VSCode Dev Container を使った開発環境構築 ] ↩︎

Facebook

関連記事 | Related Posts

We are hiring!

【スクラッチ開発エンジニア】プラットフォームG/東京・大阪

プラットフォームグループについてAWS を中心とするインフラ上で稼働するアプリケーション運用改善のサポートを担当しています。

【プラットフォームエンジニア】プラットフォームG/東京・大阪

プラットフォームグループについてAWS を中心とするインフラ上で稼働するアプリケーション運用改善のサポートを担当しています。