昨年、私は「Spring Boot 3.0 へのアップグレードガイド ~Spring Data JPA と Querydsl 編~」という記事を書きました。これはSpring Boot 3.0.x へのアップグレードに関するものでした。現在、Spring Boot 3.2 がリリースされています。Spring Boot 3.2.2 へのアップグレード時に遭遇する可能性のある2つの問題を見てみましょう。
SATプロジェクトで使用されている技術は以下の通りです:
- Spring Boot 3.2.2 および Spring Framework 6.1.3
- Hibernate + JPA モデルジェネレータ 6.4.1. Final
- Spring Data JPA 3.2.2
- Querydsl 5.0.0.
変更点
Spring Boot 3.2 におけるすべての変更点は、Spring Boot 3.2 リリースノートおよびバージョン 6.1 の新機能でSpring Framework 6.1 について説明されています。
Spring Boot 3.2.2 の最新の変更点はGitHubで確認できます。
発見された問題
- A different treatment of Hibernate dependencies due to the changed
hibernate-jpamodelgen
behavior for annotation processors Unpaged
クラスが再設計されました。
Hibernateの依存関係から始めましょう。
静的メタモデル生成の統合
最大の変更は、静的メタモデルを生成するhibernate-jpamodelgen
依存関係から来ています。Hibernate 6.3では、transitive dependenciesを緩和するために依存関係の扱いが変更されました。Spring Boot 3.2.0はhibernate-jpamodelgen
依存関係を6.3バージョンにアップグレードしました(依存関係のアップグレードを参照)。残念ながら、新しいバージョンはコンパイルエラーを引き起こします(以下参照)。
注意: ここで使用されているSpring Boot 3.2.2は既に同じ動作をするHibernate 6.4を使用しています。
コンパイルエラー
この変更により、Spring Boot 3.2.2でのプロジェクトのコンパイル(Mavenビルド)がこのようなエラーで失敗します:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.049 s
[INFO] Finished at: 2024-01-05T08:43:10+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project sat-jpa: Compilation failure: Compilation failure:
[ERROR] on the class path. A future release of javac may disable annotation processing
[ERROR] unless at least one processor is specified by name (-processor), or a search
[ERROR] path is specified (--processor-path, --processor-module-path), or annotation
[ERROR] processing is enabled explicitly (-proc:only, -proc:full).
[ERROR] Use -Xlint:-options to suppress this message.
[ERROR] Use -proc:none to disable annotation processing.
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\city\CityRepository.java:[3,41] error: cannot find symbol
[ERROR] symbol: class City_
[ERROR] location: package com.github.aha.sat.jpa.city
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\city\CityRepository.java:[3] error: static import only from classes and interfaces
...
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryCustomRepositoryImpl.java:[4] error: static import only from classes and interfaces
[ERROR] java.lang.NoClassDefFoundError: net/bytebuddy/matcher/ElementMatcher
[ERROR] at org.hibernate.jpamodelgen.validation.ProcessorSessionFactory.<clinit>(ProcessorSessionFactory.java:69)
[ERROR] at org.hibernate.jpamodelgen.annotation.AnnotationMeta.handleNamedQuery(AnnotationMeta.java:104)
[ERROR] at org.hibernate.jpamodelgen.annotation.AnnotationMeta.handleNamedQueryRepeatableAnnotation(AnnotationMeta.java:78)
[ERROR] at org.hibernate.jpamodelgen.annotation.AnnotationMeta.checkNamedQueries(AnnotationMeta.java:57)
[ERROR] at org.hibernate.jpamodelgen.annotation.AnnotationMetaEntity.init(AnnotationMetaEntity.java:297)
[ERROR] at org.hibernate.jpamodelgen.annotation.AnnotationMetaEntity.create(AnnotationMetaEntity.java:135)
[ERROR] at org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor.handleRootElementAnnotationMirrors(JPAMetaModelEntityProcessor.java:360)
[ERROR] at org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor.processClasses(JPAMetaModelEntityProcessor.java:203)
[ERROR] at org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor.process(JPAMetaModelEntityProcessor.java:174)
[ERROR] at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:1021)
[ER...
[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:348)
[ERROR] Caused by: java.lang.ClassNotFoundException: net.bytebuddy.matcher.ElementMatcher
[ERROR] at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
[ERROR] at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
[ERROR] at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
[ERROR] ... 51 more
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
これは、Hibernate移行ガイドで発表された静的メタモデル生成の変更されたアプローチによるものです(静的メタモデル生成の統合および元の問題HHH-17362を参照)。そのような変更の理由は次のとおりです:
“…以前のバージョンのHibernate ORMでは、
hibernate-jpamodelgen
の依存関係を知らずにコンパイルクラスパスにリークしていました。Hibernate ORM 6.3では、Antlrクラスが不足していることに関連するアノテーション処理中のコンパイルエラーが発生する可能性があります。”
依存関係の変更
下のスクリーンショットからわかるように、Hibernateの依存関係は実際に変更されました。
- Spring Boot 3.1.6:
- Spring Boot 3.2.2:
説明
移行ガイドで述べられているように、Mavenの単純な依存関係からMavenコンパイラプラグインのアノテーションプロセッサパスにpom.xml
を変更する必要があります(ドキュメントを参照)。
解決策
最後の記事で推奨されているように、Maven依存関係hibernate-jpamodelgen
とquerydsl-apt
(この場合)を削除できます。代わりに、pom.xml
はmaven-compiler-plugin
を介して静的メタモデルジェネレータを次のように定義する必要があります:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate.version}</version>
</path>
<path>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<classifier>jakarta</classifier>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
GitHub上のSATプロジェクトの関連する変更を参照してください。
このアプローチを使用することが強制されているため、hibernate-jpamodelgen
に関連するすべての依存関係、特にアノテーション処理に関連するもの(querydsl-apt
やlombok
など)に適用する必要があります。例えば、lombok
をこの方法で使用しない場合、次のようなコンパイルエラーが発生します:
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\city\CityService.java:[15,30] error: variable repository not initialized in the default constructor
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.535 s
[INFO] Finished at: 2024-01-08T08:40:29+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project sat-jpa: Compilation failure
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\city\CityService.java:[15,30] error: variable repository not initialized in the default constructor
同様に、querydsl-apt
についても、次のようなコンパイルエラーが見られます:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.211 s
[INFO] Finished at: 2024-01-11T08:39:18+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project sat-jpa: Compilation failure: Compilation failure:
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryRepository.java:[3,44] error: cannot find symbol
[ERROR] symbol: class QCountry
[ERROR] location: package com.github.aha.sat.jpa.country
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryRepository.java:[3] error: static import only from classes and interfaces
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryCustomRepositoryImpl.java:[3,41] error: cannot find symbol
[ERROR] symbol: class QCity
[ERROR] location: package com.github.aha.sat.jpa.city
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryCustomRepositoryImpl.java:[3] error: static import only from classes and interfaces
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryCustomRepositoryImpl.java:[4,44] error: cannot find symbol
[ERROR] symbol: class QCountry
[ERROR] location: package com.github.aha.sat.jpa.country
[ERROR] <SAT_PATH>\sat-jpa\src\main\java\com\github\aha\sat\jpa\country\CountryCustomRepositoryImpl.java:[4] error: static import only from classes and interfaces
[ERROR] -> [Help 1]
理由は明らかです。すべてのアノテーションプロセッサを同時に適用する必要があります。そうしないと、一部のコードが欠落し、コンパイルエラーが発生します。
Unpaged 再設計
2つ目の小さな問題は、Unpaged
クラスの変更に関連しています。PageImpl
のシリアル化が、Unpaged
をenum
からclass
に変更することで影響を受けました(spring-projects/spring-data-commons#2987を参照)。
- Spring Boot 3.1.6:
public interface Pageable {
static Pageable unpaged() {
return Unpaged.INSTANCE;
}
...
}
enum Unpaged implements Pageable {
INSTANCE;
...
}
- Spring Boot 3.2.2:
public interface Pageable {
static Pageable unpaged() {
return unpaged(Sort.unsorted());
}
static Pageable unpaged(Sort sort) {
return Unpaged.sorted(sort);
}
...
}
final class Unpaged implements Pageable {
private static final Pageable UNSORTED = new Unpaged(Sort.unsorted());
...
}
new PageImpl<City>(cities)
を使用すると(私たちが慣れ親しんでいた方法で)、このエラーが発生します:
2024-01-11T08:47:56.446+01:00 WARN 5168 --- [sat-elk] [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.UnsupportedOperationException)]
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/cities/country/Spain
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.github.aha.sat.elk.city.CityController
Method = com.github.aha.sat.elk.city.CityController#searchByCountry(String, Pageable)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotWritableException
回避策は、すべての属性を持つコンストラクタを使用することです:
new PageImpl<City>(cities, ofSize(PAGE_SIZE), cities.size())
代わりに:
new PageImpl<City>(cities)
注意: Spring Boot 3.3で修正される予定です(このissueコメントを参照)。
結論
本記事では、最新バージョンのSpring Boot 3.2.2へのアップグレード時に発生した問題について取り上げました(執筆時点で)。記事では、Hibernateの依存関係管理が変更されたことによるアノテーションプロセッサの扱いから始めました。次に、Unpaged
クラスの変更とPageImpl
を使用するための回避策について説明しました。
これらの変更(その他の変更も含む)はPR #64で確認できます。上記で実演した完全なソースコードは、私のGitHubリポジトリで入手できます。
Source:
https://dzone.com/articles/upgrade-guide-to-spring-boot-32-for-spring-data-jp