No ano passado, escrevi o artigo “Upgrade Guide To Spring Boot 3.0 for Spring Data JPA and Querydsl” para a atualização do Spring Boot 3.0.x. Agora, temos o Spring Boot 3.2. Vamos ver dois problemas que você pode enfrentar ao atualizar para o Spring Boot 3.2.2.
As tecnologias utilizadas no projeto SAT são:
- Spring Boot 3.2.2 e Spring Framework 6.1.3
- Hibernate + Gerador de Modelo JPA 6.4.1. Final
- Spring Data JPA 3.2.2
- Querydsl 5.0.0.
Mudanças
Todas as mudanças no Spring Boot 3.2 estão descritas em Spring Boot 3.2 Release Notes e What’s New in Version 6.1 para o Spring Framework 6.1.
As últimas mudanças no Spring Boot 3.2.2 podem ser encontradas no GitHub.
Problemas Encontrados
- A different treatment of Hibernate dependencies due to the changed
hibernate-jpamodelgen
behavior for annotation processors Unpaged
class foi redesenhada.
Vamos começar com as dependências do Hibernate primeiro.
Integrando Geração de Metamodelo Estático
A maior mudança vem da dependência hibernate-jpamodelgen
, que está gerando um metamodelo estático. No Hibernate 6.3, o tratamento das dependências foi alterado para mitigar dependências transitivas. O Spring Boot 3.2.0 atualizou a dependência hibernate-jpamodelgen
para a versão 6.3 (veja Atualizações de Dependências). Infelizmente, a nova versão causa erros de compilação (veja abaixo).
Nota: O Spring Boot 3.2.2 usado aqui já usa o Hibernate 6.4 com o mesmo comportamento.
Erro de Compilação
Com essa mudança, a compilação do nosso projeto (compilação Maven) com o Spring Boot 3.2.2 falha com erro 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
Isso é causado pela abordagem alterada na geração de metamodelo estático anunciada no guia de migração do Hibernate (veja Integrando Geração de Metamodelo Estático e a issue original HHH-17362). A explicação deles para tal mudança é esta:
“… em versões anteriores do Hibernate ORM, você estava vazando dependências de
hibernate-jpamodelgen
para o classpath de compilação sem perceber. Com o Hibernate ORM 6.3, agora você pode enfrentar um erro de compilação durante o processamento de anotações relacionado a classes Antlr ausentes.”
Mudanças nas Dependências
Como você pode ver nas capturas de tela abaixo, as dependências do Hibernate realmente foram alteradas.
- Spring Boot 3.1.6:
- Spring Boot 3.2.2:
Explicação
Como mencionado na guia de migração, precisamos mudar nosso pom.xml
de uma simples dependência Maven para os caminhos de processadores de anotações do plugin de compilador Maven (veja documentação).
Solução
Podemos remover as dependências Maven hibernate-jpamodelgen
e querydsl-apt
(no nosso caso) como recomendado no último artigo. Em vez disso, o pom.xml
deve definir os geradores de metamodelos estáticos via maven-compiler-plugin
desta maneira:
<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>
Veja as alterações relacionadas no projeto SAT no GitHub.
Como somos forçados a usar essa abordagem devido a hibernate-jpamodelgen
, precisamos aplicá-la a todas as dependências relacionadas ao processamento de anotações (querydsl-apt
ou lombok
). Por exemplo, quando lombok
não é usado dessa maneira, recebemos um erro de compilação 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
O mesmo se aplica a querydsl-apt
. Neste caso, podemos ver um erro de compilação 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]
A razão é óbvia. Precisamos aplicar todos os processadores de anotações ao mesmo tempo. Caso contrário, algumas partes do código podem ficar faltando, e recebemos um erro de compilação.
Redesenhado sem paginação
O segundo problema menor está relacionado a uma mudança na classe Unpaged
. A serialização de PageImpl
pela biblioteca Jackson foi afetada pela mudança de Unpaged
de enum
para class
(veja 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());
...
}
Quando new PageImpl<City>(cities)
é usado (como costumávamos usá-lo), então este erro é lançado:
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
A solução alternativa é usar o construtor com todos os atributos como:
new PageImpl<City>(cities, ofSize(PAGE_SIZE), cities.size())
Ao invés de:
new PageImpl<City>(cities)
Nota: Deve ser corrigido no Spring Boot 3.3 (veja este comentário de problema).
Conclusão
Este artigo cobriu tanto os problemas encontrados ao atualizar para a versão mais recente do Spring Boot 3.2.2 (no momento de escrever este artigo). O artigo começou com o tratamento dos processadores de anotações devido à mudança na gestão de dependências do Hibernate. Em seguida, foi explicada a mudança na classe Unpaged
e a solução alternativa para usar PageImpl
.
Todas essas mudanças (juntamente com outras mudanças) podem ser vistas em PR #64. O código-fonte completo demonstrado acima está disponível no meu repositório GitHub.
Source:
https://dzone.com/articles/upgrade-guide-to-spring-boot-32-for-spring-data-jp