Spring Data JPA 中的 “Not a Managed Type” 异常
1. 概述
在使用 Spring Data JPA 时,我们可能会在引导过程中遇到问题。一些 bean 可能无法创建,导致应用无法启动。虽然实际的堆栈跟踪可能会有所不同,但通常看起来是这样的:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerAdapter'
...
Caused by: java.lang.IllegalArgumentException: Not a managed type: ...OurEntity
at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:583)
at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:85)
...
根本原因是 “Not a managed type” 异常。本文中,我们将深入研究此异常的可能原因并探讨其解决方案。
2. 缺少 @Entity 注解
出现此异常的一个可能原因是,我们可能忘记使用 @Entity
注解标记实体。
2.1. 复现问题
假定我们有以下实体类:
public class EntityWithoutAnnotation {
@Id
private Long id;
}
我们同时有它的 Spring Data JPA repository:
public interface EntityWithoutAnnotationRepository
extends JpaRepository<EntityWithoutAnnotation, Long> {
}
最后,我们有一个应用类,用来扫描上述所有的类:
@SpringBootApplication
public class EntityWithoutAnnotationApplication {
}
让我们使用该应用来引导 Spring 上下文:
@Test
void givenEntityWithoutAnnotationApplication_whenBootstrap_thenExpectedExceptionThrown() {
Exception exception = assertThrows(Exception.class,
() -> SpringApplication.run(EntityWithoutAnnotationApplication.class));
assertThat(exception)
.getRootCause()
.hasMessageContaining("Not a managed type");
}
如预期一样,我们遇到了关联实体的 “Not a managed type” 异常。
2.2. 修复改问题
让我们添加 @Entity
注解来实体的修复版本中:
@Entity
public class EntityWithoutAnnotationFixed {
@Id
private Long id;
}
应用类和 repository 类保持不变。再次尝试引导该应用:
@Test
void givenEntityWithoutAnnotationApplicationFixed_whenBootstrap_thenRepositoryBeanShouldBePresentInContext() {
ConfigurableApplicationContext context = run(EntityWithoutAnnotationFixedApplication.class);
EntityWithoutAnnotationFixedRepository repository = context
.getBean(EntityWithoutAnnotationFixedRepository.class);
assertThat(repository).isNotNull();
}
我们成功地检索到 ConfigurableApplicationContext
实例并从中获得 repository 实例。
3. 从 javax.persistance 迁移到 jakarta.persistance
当我们将应用迁移到 Jakarta 持久化 API 时,可能会遇到该异常的另一种情形。
3.1. 复现问题
假定我们有另一个实体:
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class EntityWithJakartaAnnotation {
@Id
private Long id;
}
此处我们开始使用 jakarta.persistence
包,不过我们仍然使用 Spring Boot 2。我们将使用类似于前面创建方式来创建 repository 类和应用类。
现在,我们尝试引导应用:
@Test
void givenEntityWithJakartaAnnotationApplication_whenBootstrap_thenExpectedExceptionThrown() {
Exception exception = assertThrows(Exception.class,
() -> run(EntityWithJakartaAnnotationApplication.class));
assertThat(exception)
.getRootCause()
.hasMessageContaining("Not a managed type");
}
再次出现 “Not a managed type” 异常。JPA 实体扫描器期望我们使用 javax.persistence.Entity
注解,而非 jakarta.persistence.Entity
注解。
3.2. 修复
本例中,我们有两个可能的解决方案。我们可以 Spring Boot 3 并且让 Spring Data JPA 开始使用 jakarta.persistence
。另外,如果不准备迁移,可以继续使用 javax.persistence.Entity
。
4. 缺少或错误配置了 @EntityScan
另一个会碰到 “Not a managed type” 异常的常见情况是,JPA 实体扫描器无法在期望的路径中找到实体。
4.1. 复现问题
首先,创建另一个实体:
package com.baeldung.spring.entity;
@Entity
public class CorrectEntity {
@Id
private Long id;
}
它有 @Entity
注解且位于 entity
包中。然后我们创建一个 repository:
package com.baeldung.spring.repository;
public interface CorrectEntityRepository extends JpaRepository<CorrectEntity, Long> {
}
Repository 放置在 repository
包。最后创建一个应用类:
package com.baeldung.spring.app;
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.baeldung.spring.repository")
public class WrongEntityScanApplication {
}
它在 app
包中。默认情况下,Spring Data 在 main 类包及其子包查找 repository。因此,我们需要使用 @EnableJpaRepositories
指定基础的 repository 包。然后,我们尝试引导该应用:
@Test
void givenWrongEntityScanApplication_whenBootstrap_thenExpectedExceptionThrown() {
Exception exception = assertThrows(Exception.class,
() -> run(WrongEntityScanApplication.class));
assertThat(exception)
.getRootCause()
.hasMessageContaining("Not a managed type");
}
再次出现 “Not a managed type” 异常。原因是实体的扫描逻辑与 repository 的扫描逻辑一样。扫描完 app
包下的子包后,我们没有找到任何实体,而在构造 CorrectEntityRepository
时出现异常。
4.2. 修复
要修复该问题,我们可以使用 @EntityScan
注解。
然后,我们再创建一个应用类。
@SpringBootApplication
@EnableJpaRepositories(basePackages =
"com.baeldung.spring.repository")
@EntityScan("com.baeldung.spring.entity")
public class WrongEntityScanFixedApplication {
}
现在,我们使用 @EnableJpaRepositories
注解指定 repository 包,同时使用 @EntityScan
注解指定实体包。我们来看看它如何工作的:
@Test
void givenWrongEntityScanApplicationFixed_whenBootstrap_thenRepositoryBeanShouldBePresentInContext() {
SpringApplication app = new SpringApplication(WrongEntityScanFixedApplication.class);
app.setAdditionalProfiles("test");
ConfigurableApplicationContext context = app.run();
CorrectEntityRepository repository = context
.getBean(CorrectEntityRepository.class);
assertThat(repository).isNotNull();
}
我们成功地引导了应用。从上下文中检索了 CorrectEntityRepository
,也就是说它被成功创建了并且 CorrectEntity
成功地被当成 JPA 实体。
5. 结论
本教程中,我们探讨了为什么在使用 Spring Data JPA 时可能会遇到 “Not a Managed Type” 异常。我们还学习了避免这种情况的解决方案。解决方案的选择取决于具体情况,但了解可能的原因有助于我们识别我们面临的确切情况。