Guia de Atualização para o Spring Boot 3.2 para o Projeto Spring Data JPA e Querydsl

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:

  1. Spring Boot 3.2.2 e Spring Framework 6.1.3
  2. Hibernate + Gerador de Modelo JPA 6.4.1. Final 
  3. Spring Data JPA 3.2.2
  4. Querydsl 5.0.0.

Mudanças

Todas as mudanças no Spring Boot 3.2 são descritas em Spring Boot 3.2 Release Notes e What’s New in Version 6.1 para 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 (construção Maven) com o Spring Boot 3.2.2 falha com um erro como este:

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

Isso é causado pela abordagem alterada na geração do metamodelo estático anunciada no guia de migração do Hibernate (veja Integrando Geração de Metamodelo Estático e a questão 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 alterar nosso pom.xml de uma simples dependência Maven para os caminhos de processadores de anotações do plugin de compilador Maven (veja a 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 metamodelo estático via maven-compiler-plugin desta maneira:

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>

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 à processamentos de anotações (querydsl-apt ou lombok). Por exemplo, quando lombok não é usado dessa maneira, obtemos um erro de compilação como este:

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

O mesmo se aplica a querydsl-apt. Nesse caso, podemos ver um erro de compilação como este:

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]

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 obtemos 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:
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 new PageImpl<City>(cities) é usado (como estávamos acostumados a usá-lo), então esse erro é lançado:

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

A solução alternativa é usar o construtor com todos os atributos como:

Java

 

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

Ao invés de:

Java

 

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 (na época de escrita deste 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 a classe PageImpl.

Todas essas mudanças (juntamente com algumas outras) 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