El año pasado, escribí el artículo, “Guía de actualización para Spring Boot 3.0 para Spring Data JPA y Querydsl“, para la actualización de Spring Boot 3.0.x. Ahora, tenemos Spring Boot 3.2. Veamos dos problemas que podrías enfrentar al actualizar a Spring Boot 3.2.2.
Las tecnologías utilizadas en el proyecto SAT son:
- Spring Boot 3.2.2 y Spring Framework 6.1.3
- Generador de modelo Hibernate + JPA 6.4.1. Final
- Spring Data JPA 3.2.2
- Querydsl 5.0.0.
Cambios
Todas las modificaciones en Spring Boot 3.2 se describen en Notas de lanzamiento de Spring Boot 3.2 y Novedades en la versión 6.1 para Spring Framework 6.1.
Los últimos cambios en Spring Boot 3.2.2 pueden encontrarse en GitHub.
Problemas encontrados
- A different treatment of Hibernate dependencies due to the changed
hibernate-jpamodelgen
behavior for annotation processors Unpaged
class fue rediseñada.
Comencemos con las dependencias de Hibernate primero.
Integración de Generación de Metamodelo Estático
El mayor cambio proviene de la dependencia hibernate-jpamodelgen
, que genera un metamodelo estático metamodel. En Hibernate 6.3, el manejo de dependencias se modificó para mitigar las dependencias transitivas. Spring Boot 3.2.0 actualizó la dependencia hibernate-jpamodelgen
a la versión 6.3 (ver Actualizaciones de Dependencias). Desafortunadamente, la nueva versión provoca errores de compilación (ver más abajo).
Nota: Spring Boot 3.2.2 utilizado aquí ya utiliza Hibernate 6.4 con el mismo comportamiento.
Error de Compilación
Con este cambio, la compilación de nuestro proyecto (construcción con Maven) con Spring Boot 3.2.2 falla con un error como este:
[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
Esto se debe al enfoque modificado en la generación del metamodelo estático anunciado en la guía de migración de Hibernate (ver Integración de Generación de Metamodelo Estático y el problema original HHH-17362). Su explicación para tal cambio es la siguiente:
“… en versiones anteriores de Hibernate ORM, estabas filtrando dependencias de
hibernate-jpamodelgen
en tu classpath de compilación sin darte cuenta. Con Hibernate ORM 6.3, ahora podrías experimentar un error de compilación durante el procesamiento de anotaciones relacionado con clases Antlr faltantes.”
Cambios en las Dependencias
Como se puede observar en las capturas de pantalla, las dependencias de Hibernate realmente han cambiado.
- Spring Boot 3.1.6:
- Spring Boot 3.2.2:
Explicación
Como se menciona en la guía de migración, necesitamos cambiar nuestro pom.xml
de una simple dependencia de Maven a las rutas de procesadores de anotaciones del plugin de compilador de Maven (ver documentación).
Solución
Podemos eliminar las dependencias de Maven hibernate-jpamodelgen
y querydsl-apt
(en nuestro caso) como se recomienda en el último artículo. En su lugar, pom.xml
debe definir los generadores de metamodelos estáticos a través de maven-compiler-plugin
de esta manera:
<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>
Vea los cambios relacionados en el proyecto SAT en GitHub.
Debido a que estamos obligados a utilizar este enfoque por hibernate-jpamodelgen
, necesitamos aplicarlo a todas las dependencias relacionadas con el procesamiento de anotaciones (querydsl-apt
o lombok
). Por ejemplo, cuando lombok
no se usa de esta manera, obtenemos un error de compilación como este:
[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 mismo se aplica a querydsl-apt
. En este caso, podemos ver el error de compilación como este:
[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 razón es evidente. Necesitamos aplicar todos los procesadores de anotaciones al mismo tiempo. De lo contrario, algunas partes del código pueden faltar, y obtenemos un error de compilación.
Rediseño sin paginar
El segundo problema menor está relacionado con un cambio en la clase Unpaged
. La serialización de PageImpl
por la biblioteca Jackson se vio afectada al cambiar Unpaged
de enum
a class
(ver 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());
...
}
Cuando se utiliza new PageImpl<City>(cities)
(como solíamos usarlo), entonces se lanza este error:
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 solución alternativa es usar el constructor con todos los atributos como:
new PageImpl<City>(cities, ofSize(PAGE_SIZE), cities.size())
En lugar de:
new PageImpl<City>(cities)
Nota: Debería solucionarse en Spring Boot 3.3 (ver este comentario de problema).
Conclusión
Este artículo ha abordado tanto los problemas encontrados al actualizar a la versión más reciente de Spring Boot 3.2.2 (al momento de escribir este artículo). El artículo comenzó con el manejo de los procesadores de anotaciones debido al cambio en la gestión de dependencias de Hibernate. A continuación, se explicó el cambio en la clase Unpaged
y la solución alternativa para usar PageImpl
.
Todas estas modificaciones (junto con otras) pueden verse en PR #64. El código fuente completo mostrado anteriormente está disponible en mi repositorio de GitHub.
Source:
https://dzone.com/articles/upgrade-guide-to-spring-boot-32-for-spring-data-jp