当前位置:Java -> Querydsl vs. JPA Criteria,第6部分:Spring Boot 3.2升级指南,适用于Spring Data JPA和Querydsl项目

Querydsl vs. JPA Criteria,第6部分:Spring Boot 3.2升级指南,适用于Spring Data JPA和Querydsl项目

去年,我为Spring Boot 3.0.x 升级写了一篇文章《Spring Boot 3.0升级指南:Spring Data JPA和Querydsl》。现在,我们有了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. Final
  3. Spring Data JPA 3.2.2
  4. Querydsl 5.0.0.

变化

Spring Boot 3.2 中的所有变化在《Spring Boot 3.2发布说明》以及Spring Framework 6.1的《6.1版本的新功能》中有描述。

Spring Boot 3.2.2 的最新变化可以在GitHub上找到。

发现的问题

  • 由于注解处理器hibernate-jpamodelgen行为的改变,对Hibernate依赖的处理方式不同
  • 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上的编译失败,出现如下错误:

[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.1.6.

  • Spring Boot 3.2.2:

依赖变化:Spring Boot 3.2.2

解释

正如迁移指南中所述,我们需要将pom.xml从一个简单的Maven依赖更改为Maven编译器插件的注解处理器路径(参见文档)。

解决方案

我们可以按照上一篇文章的建议,移除Maven依赖hibernate-jpamodelgenquerydsl-apt。相反,pom.xml必须通过maven-compiler-plugin定义静态元模型生成器,如下所示:

<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>


在GitHub上查看SAT 项目中的相关变更

由于 hibernate-jpamodelgen 的影响,我们被迫使用这种方法,需要将其应用到所有与注解处理相关的依赖项(querydsl-aptlombok)。例如,当未以这种方式使用 lombok 时,我们会收到以下编译错误:

[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 也是如此。在这种情况下,我们可以看到以下编译错误:

[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 类的变化有关。Jackson 库对 PageImpl 的序列化受到了 Unpagedenum 更改为 class 的影响(见 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());

	...

}


当使用 new PageImpl<City>(cities)(正如我们习惯使用的)时,将出现此错误:

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


解决方法是使用带有所有属性的构造函数:

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


而不是:

new PageImpl<City>(cities)


注意:应该在 Spring Boot 3.3 中修复此问题(参见 此问题评论)。

结论

本文介绍了升级到最新版本 Spring Boot 3.2.2 时发现的两个问题。文章首先讨论了由于改变的 Hibernate 依赖管理而处理注解处理器的情况。接下来,解释了 Unpaged 类的变化以及使用 PageImpl 的解决方法。

上述所有更改(和其他一些更改)都可以在 PR #64 中查看。上述完整源代码可在我的 GitHub 仓库中找到。

推荐阅读: 13.ArrayList和LinkedList的区别是什么?

本文链接: Querydsl vs. JPA Criteria,第6部分:Spring Boot 3.2升级指南,适用于Spring Data JPA和Querydsl项目