GraalVM编译Spring Boot应用启动报错:ConfigLog不是托管类型
最近我在把基于Java 17、Spring Boot 3.4.1的项目迁移到GraalVM上,用OpenJDK 17.0.14运行时一切正常,用GraalVM也能顺利完成构建,但运行生成的可执行文件时直接报错了,提示ConfigLog不是托管类型。我用的GraalVM版本是GraalVM Liberica-NIK-23.0.7-1 (build 17.0.14+10-LTS)。
核心报错堆栈
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configLogRepository': Not a managed type: class com.project.demo.module.configLogs.domain.ConfigLog at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:289) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1573) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1519) at org.springframework.beans.factory.aot.AutowiredFieldValueResolver.resolveValue(AutowiredFieldValueResolver.java:186) ... 192 more Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.project.demo.module.configLogs.domain.ConfigLog at org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl.managedType(JpaMetamodelImpl.java:223) at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.managedType(MappingMetamodelImpl.java:470) at org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl.managedType(MappingMetamodelImpl.java:100) at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:82) at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getEntityInformation(JpaEntityInformationSupport.java:69) at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:251) at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:215) at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:198) at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:1) at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:386) at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$4(RepositoryFactoryBeanSupport.java:350) at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) at org.springframework.data.util.Lazy.get(Lazy.java:113) at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:356) at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ... 201 more
我的相关配置与代码
数据库配置类(DatabaseConfiguration)
package com.project.demo.config; import com.zaxxer.hikari.HikariDataSource; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import lombok.AllArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.context.EnvironmentAware; import jakarta.persistence.EntityManagerFactory; @Configuration @EnableTransactionManagement @EnableJpaRepositories(entityManagerFactoryRef = "postgresEntityManagerFactory", transactionManagerRef = "postgresTransactionManager", basePackages = "com.project.demo") @AllArgsConstructor public class DatabaseConfiguration implements EnvironmentAware { private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Primary @Bean(name = "postgresDataSource") @ConfigurationProperties(prefix = "spring.datasource") public HikariDataSource dataSource() { return DataSourceBuilder.create().type(HikariDataSource.class) .driverClassName("org.postgresql.Driver") .url("jdbc:postgresql://localhost:5432/demo") .username(environment.getProperty("spring.datasource.username")) .password(environment.getProperty("spring.datasource.password")).build(); } public Map<String, String> hikariConfig() { Map<String, String> properties = new HashMap<>(); properties.put("driverClassName", "org.postgresql.Driver"); properties.put("url", "jdbc:postgresql://localhost:5432/demo"); properties.put("username", environment.getProperty("spring.datasource.username")); properties.put("password", environment.getProperty("spring.datasource.password")); properties.put("minimumIdle", "5"); properties.put("maximumPoolSize", "8"); properties.put("idleTimeout", "30000"); properties.put("poolName", "SpringBootJPAHikariCP"); properties.put("maxLifetime", "600000"); properties.put("keepaliveTime", "60000"); properties.put("connectionTimeout", "30000"); properties.put("initializationFailTimeout", "10000"); properties.put("minimumIdle", "10000"); return properties; } @Bean(name = "postgresTransactionManager") public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } @Bean(name = "postgresEntityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder entityManagerFactoryBuilder, @Qualifier("postgresDataSource") DataSource dataSource) { return entityManagerFactoryBuilder.dataSource(dataSource).packages("com.project.demo").properties(hikariConfig()).build(); } }
ConfigLog实体类
package com.project.demo.module.configLogs.domain; import com.project.demo.module.masters.constants.OperationType; import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; import java.time.LocalDateTime; @Entity @Table(name = "config_log") @Getter @Setter public class ConfigLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "user_id") private Long userId; @Column(name = "company_id") private Long companyId; @Column(name = "operation_type") @Enumerated(value = EnumType.STRING) private OperationType operationType; @Column(name = "log_time") private LocalDateTime logTime = LocalDateTime.now(); @Column(name = "json") private String json; @Override public String toString() { return "ConfigLog{" + "id=" + id + ", userId=" + userId + ", companyId=" + companyId + ", operationType=" + operationType + ", logTime=" + logTime + ", json='" + json + '\'' + '}'; } }
ConfigLogRepository类
package com.project.demo.module.configLogs.repository; import com.project.demo.module.configLogs.domain.ConfigLog; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ConfigLogRepository extends JpaRepository<ConfigLog, Long> { }
问题分析与解决方案
这个问题本质是GraalVM在编译原生镜像时,没有正确识别到JPA实体类的反射信息,导致Hibernate无法将其标记为托管类型。下面是几个可行的解决方案:
1. 显式在EntityManagerFactory中指定实体类
修改数据库配置类中的entityManagerFactory方法,在构建时显式添加实体类,让GraalVM编译时能明确扫描到:
@Bean(name = "postgresEntityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder entityManagerFactoryBuilder, @Qualifier("postgresDataSource") DataSource dataSource) { return entityManagerFactoryBuilder .dataSource(dataSource) .packages("com.project.demo") .properties(hikariConfig()) // 显式添加实体类,多个的话可以用逗号分隔 .classes(ConfigLog.class) .build(); }
2. 添加@RegisterReflectionForBinding注解
在Spring Boot主应用类上添加该注解,强制GraalVM保留实体类的反射元数据:
@SpringBootApplication @RegisterReflectionForBinding(ConfigLog.class) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
如果有多个实体类,可以用数组形式:@RegisterReflectionForBinding({ConfigLog.class, YourOtherEntity.class})
3. 优化Spring Boot原生镜像插件配置
如果你用的是Spring Boot的原生镜像插件,确保插件配置中包含必要的参数,帮助GraalVM识别实体类。比如Maven插件可以这样配置:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <nativeImage> <buildArgs> <!-- 指定实体类所在的包,确保扫描到 --> -H:IncludeResources=application.properties -H:Package=com.project.demo.module.configLogs.domain </buildArgs> </nativeImage> </configuration> </plugin>
4. 确认注解正确性
虽然你的代码里已经用了Jakarta Persistence的注解(jakarta.persistence.*),但还是要确认没有混用旧的Javax注解,Spring Boot 3.x完全基于Jakarta EE,混用会导致识别问题。
备注:内容来源于stack exchange,提问作者Vibhor Verma

