دليل الترقية إلى Spring Boot 3.2 لمشروع Spring Data JPA و Querydsl مقابل JPA Criteria، الجزء 6

العام الماضي، كتبت المقال، “دليل الترقية إلى Spring Boot 3.0 لـ Spring Data JPA و Querydsl“، لترقية Spring Boot 3.0.x. الآن، لدينا Spring Boot 3.2. دعونا نرى سببين قد تواجههما عند الترقية إلى Spring Boot 3.2.2.

التقنيات المستخدمة في مشروع SAT هي:

  1. Spring Boot 3.2.2 و Spring Framework 6.1.3
  2. Hibernate + مولد نموذج JPA 6.4.1. نهائي 
  3. Spring Data JPA 3.2.2
  4. 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 على خطأ مثل هذا:

Plain Text

 

[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 هكذا:

XML

 

<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 بهذه الطريقة، نحصل على خطأ الترجمة كما هو موضح في:

Plain Text

 

[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. في هذه الحالة، يمكننا رؤية خطأ الترجمة كما يلي:

Plain Text

 

[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:
Java

 

public interface Pageable {

	static Pageable unpaged() {
		return Unpaged.INSTANCE;
	}

	...
}

enum Unpaged implements Pageable {

	INSTANCE;

	...
}

  • Spring Boot 3.2.2:
Java

 

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) (كما اعتدنا على استخدامه)، ثم يتم طرح هذا الخطأ:

Plain Text

 

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

الحل البديل هو استخدام المُنشئ بجميع السمات كما يلي:

Java

 

new PageImpl<City>(cities, ofSize(PAGE_SIZE), cities.size())

بدلاً من:

Java

 

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