Spring Boot 2使用hibernate-jpamodelgen时的重复注解问题
问题背景
我有一个基于Spring Boot 2.0.2.RELEASE(也在2.0.0.RELEASE中复现)的简单应用,使用hibernate-jpamodelgen生成JPA元模型。完整的Maven POM如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> </parent> <groupId>gov.idaho.isp</groupId> <artifactId>SpringBootJpaModelGenIssue</artifactId> <version>0.1</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <tomcat.version>9.0.5</tomcat.version> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> </dependency> </dependencies> </project>
应用包含一个简单实体Widget,其中name字段带有@NotNull校验注解:
import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.validation.constraints.NotNull; @Entity public class Widget implements Serializable { @Id @GeneratedValue private Long id; @NotNull private String name; // 省略getter/setter等方法 }
当运行应用并修改这个实体类后,应用无法成功重启,抛出以下错误:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.cfg.beanvalidation.IntegrationException: Error activating Bean Validation integration
最终根因是:
Caused by: java.lang.annotation.AnnotationFormatError: Duplicate annotation for class: interface javax.validation.constraints.NotNull: @javax.validation.constraints.NotNull(message={javax.validation.constraints.NotNull.message}, groups=[], payload=[])
移除hibernate-jpamodelgen依赖后,重启错误消失。请问这是已知问题吗?
专家解答
你遇到的这个问题确实是Spring Boot 2.x早期版本与hibernate-jpamodelgen结合使用时的已知兼容性问题,核心冲突点在于Spring Boot DevTools的热重启机制和JPA元模型生成的交互逻辑。
问题本质
Spring Boot DevTools的自动重启功能会在检测到类文件变化时,重新加载相关类。而hibernate-jpamodelgen会在编译时生成JPA元模型类(默认存放在target/generated-sources/annotations目录下),这些生成的类会引用原始实体类的注解信息。当热重启触发时,DevTools会同时重新加载原始实体类和生成的元模型类,导致Bean Validation注解(如@NotNull)被重复注册到类元数据中,最终触发AnnotationFormatError。
解决办法
这里有几个实用的解决方案,你可以根据开发需求选择:
- 排除元模型目录的热重启监控
在application.properties中添加配置,让DevTools忽略元模型生成目录的变化,避免重复加载:
spring.devtools.restart.exclude=target/generated-sources/**
- 限定
hibernate-jpamodelgen的依赖范围
将hibernate-jpamodelgen的依赖范围设置为provided,这样它仅在编译阶段用于生成元模型,运行时(包括热重启)不会被加载,从根源避免冲突:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <scope>provided</scope> </dependency>
- 禁用DevTools自动重启
如果你的开发流程不需要热重启功能,可以直接关闭该特性:
spring.devtools.restart.enabled=false
版本修复说明
这个问题在Spring Boot 2.1.x及后续版本中得到了优化,同时Hibernate后续版本的hibernate-jpamodelgen也调整了注解处理逻辑,减少了类加载冲突的概率。如果项目允许升级版本,这也是一个长期的解决方案。
内容的提问来源于stack exchange,提问作者Brice Roncace

