العام الماضي، كتبت المقال، “دليل الترقية إلى Spring Boot 3.0 لـ Spring Data JPA و Querydsl“، لترقية Spring Boot 3.0.x. الآن، لدينا Spring Boot 3.2. دعونا نرى سببين قد تواجههما عند الترقية إلى Spring Boot 3.2.2.
التقنيات المستخدمة في مشروع SAT هي:
- Spring Boot 3.2.2 و Spring Framework 6.1.3
- Hibernate + مولد نموذج JPA 6.4.1. نهائي
- 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، تم تغيير معالجة العوائد من أجل تخفيف العوائد المتنقلة. Spring Boot 3.2.0 زاد من العوائد hibernate-jpamodelgen
إلى الإصدار 6.3 (انظر الترقيات التابعة). لسوء الحظ، يسبب الإصدار الجديد أخطاء تجميع (انظر أدناه).
ملاحظة: يستخدم Spring Boot 3.2.2 هنا بالفعل Hibernate 6.4 بنفس السلوك.
خطأ التجميع
مع هذا التغيير، فشل تجميع مشروعنا (بناء Maven) مع Spring Boot 3.2.2 على خطأ مثل هذا:
[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:
شرح
كما ذكر في دليل الترحيل، نحتاج إلى تغيير 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>
انظر إلى التغييرات المتعلقة بمشروع SAT على GitHub.
لأننا مجبرون على استخدام هذا الأسلوب بسبب 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
. تأثرت عملية تحليل 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 (انظر تعليق هذه المشكلة).
الخاتمة
هذا المقال قد غطى كل من المشكلات الموجودة عند ترقية إلى أحدث إصدار من Spring Boot 3.2.2 (في وقت كتابة هذا المقال). بدأ المقال بالتعامل مع معالجي التعليمات البرمجية بسبب تغيير إدارة العوامل المستندة إلى Hibernate. بعد ذلك، تم شرح تغيير فئة Unpaged
والحل البديل لاستخدام PageImpl
.
يمكن رؤية كل التغييرات (ببعض التغييرات الأخرى) في مكالمة اتصال #64. يتوفر الكود المصدري الكامل الموضح أعلاه في مستودعي GitHub.
Source:
https://dzone.com/articles/upgrade-guide-to-spring-boot-32-for-spring-data-jp