Guida all’aggiornamento a Spring Boot 3.2 per il progetto Spring Data JPA e Querydsl vs. JPA Criteria, Parte 6

L’anno scorso, ho scritto l’articolo, “Guida all’aggiornamento per Spring Boot 3.0 per Spring Data JPA e Querydsl,” per l’aggiornamento a Spring Boot 3.0.x. Ora, abbiamo Spring Boot 3.2. Vediamo due problemi che potresti incontrare durante l’aggiornamento a Spring Boot 3.2.2.

Le tecnologie utilizzate nel progetto SAT sono:

  1. Spring Boot 3.2.2 e Spring Framework 6.1.3
  2. Hibernate + generatore di modelli JPA 6.4.1. Final 
  3. Spring Data JPA 3.2.2
  4. Querydsl 5.0.0.

Modifiche

Tutte le modifiche in Spring Boot 3.2 sono descritte in Note sulla versione di Spring Boot 3.2 e Novità nella versione 6.1 per Spring Framework 6.1.

Le ultime modifiche in Spring Boot 3.2.2 possono essere trovate su GitHub.

Problemi Riscontrati

  • A different treatment of Hibernate dependencies due to the changed hibernate-jpamodelgen behavior for annotation processors
  • Unpaged class redesigned.

Cominciamo prima con le dipendenze di Hibernate.

Integrazione della Generazione del Metamodello Statico

Il cambiamento più significativo deriva dalla dipendenza hibernate-jpamodelgen, che genera un metamodello statico metamodello. In Hibernate 6.3, il trattamento delle dipendenze è stato modificato per mitigare le dipendenze transitive. Spring Boot 3.2.0 ha aggiornato la dipendenza hibernate-jpamodelgen alla versione 6.3 (vedi Aggiornamenti delle Dipendenze). Sfortunatamente, la nuova versione causa errori di compilazione (vedi sotto).

Nota: Spring Boot 3.2.2 utilizzato qui già utilizza Hibernate 6.4 con lo stesso comportamento.

Errore di Compilazione

Con questo cambiamento, la compilazione del nostro progetto (build Maven) con Spring Boot 3.2.2 fallisce con un errore simile a questo:

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

Questo è causato dall’approccio modificato nella generazione del metamodello statico annunciato nella guida di migrazione di Hibernate (vedi Integrazione della Generazione del Metamodello Statico e l’issue originale HHH-17362). La loro spiegazione per questo cambiamento è la seguente:

“… in versioni precedenti di Hibernate ORM si verificava uno smarrimento di dipendenze di hibernate-jpamodelgen nel percorso di compilazione senza rendersene conto. Con Hibernate ORM 6.3, potresti ora riscontrare un errore di compilazione durante il processamento delle annotazioni a causa della mancanza di classi Antlr.”

Modifiche alle Dipendenze

Come puoi vedere nelle schermate qui sotto, le dipendenze di Hibernate sono state davvero modificate.

  • Spring Boot 3.1.6:

  • Spring Boot 3.2.2:

Spiegazione

Come indicato nella guida di migrazione, è necessario modificare il nostro pom.xml da una semplice dipendenza Maven alle path dei processori di annotazioni del plugin di compilatore Maven (vedi documentazione).

Soluzione

Possiamo rimuovere le dipendenze Maven hibernate-jpamodelgen e querydsl-apt (nel nostro caso) come raccomandato nell’ultimo articolo. Invece, pom.xml deve definire i generatori di modelli metamodello statici tramite maven-compiler-plugin in questo modo:

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>

Vedi le modifiche correlate nel progetto SAT su GitHub.

Poiché siamo costretti a utilizzare questo approccio a causa di hibernate-jpamodelgen, dobbiamo applicarlo a tutte le dipendenze strettamente legate alla elaborazione delle annotazioni (querydsl-apt o lombok). Ad esempio, quando lombok non viene utilizzato in questo modo, otteniamo un errore di compilazione simile a questo:

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

Lo stesso vale per querydsl-apt. In questo caso, possiamo vedere un errore di compilazione simile a questo:

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]

La ragione è ovvia. Dobbiamo applicare tutti i processori di annotazioni contemporaneamente. Altrimenti, alcuni pezzi di codice potrebbero mancare e otteniamo un errore di compilazione.

Ridisegnato Non Paginato

Il secondo problema minore è legato a un cambiamento nella classe Unpaged. La serializzazione di PageImpl da parte della libreria Jackson è stata influenzata dal cambiamento di Unpaged da enum a class (vedi 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());

	...

}

Quando viene utilizzato new PageImpl<City>(cities) (come eravamo abituati a usarlo), viene lanciato questo errore:

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

La soluzione alternativa è utilizzare il costruttore con tutti gli attributi come:

Java

 

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

Invece di:

Java

 

new PageImpl<City>(cities)

Nota: Dovrebbe essere risolto in Spring Boot 3.3 (vedi questo commento dell’issue).

Conclusione

Questo articolo ha trattato sia i problemi riscontrati durante l’aggiornamento alla versione più recente di Spring Boot 3.2.2 (al momento della stesura di questo articolo). L’articolo è iniziato con la gestione dei processori di annotazioni a causa del cambiamento nella gestione delle dipendenze di Hibernate. Successivamente, è stato spiegato il cambiamento nella classe Unpaged e la soluzione alternativa per l’utilizzo della classe PageImpl.

Tutte le modifiche (insieme ad altre modifiche) possono essere osservate in PR #64. Il codice sorgente completo dimostrato sopra è disponibile nel mio GitHub repository.

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