פנקס קפדן ו-JPA Criteria, חלק 6: מדריך עדכון ל-Spring Boot 3.2 עבור פרויקט Spring Data JPA ו-Querydsl

לשנה שעברה, כתבתי את המאמר, "מדריך עדכון ל-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 פשוטות לנתיבי עיבוד הסימניות של פלטפורמת המהדר Maven (ראה תיעוד).

פתרון

ניתן להסיר את התלותים של 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 על ידי ספריית ה-Jackson הושפעה משינוי 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 (בעת כתיבת מאמר זה). המאמר התחיל בטיפול במפרשי העיצוב עקב הניהול התלוי של היסברניה. לאחר מכן, נתבא על השינוי במחלקה Unpaged ופיתרון נגדי לשימוש בPageImpl.

כל השינויים (יחד עם שינויים נוספים) ניתן לראות בPR #64. הקוד המקורי המדגים לעיל זמין במאגר הGitHub שלי.

Source:
https://dzone.com/articles/upgrade-guide-to-spring-boot-32-for-spring-data-jp