编程

Spring Data JPA 中的 “Not a Managed Type” 异常

593 2024-08-13 03:31:00

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” 异常。我们还学习了避免这种情况的解决方案。解决方案的选择取决于具体情况,但了解可能的原因有助于我们识别我们面临的确切情况。