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

根本原因是一个“非受管理类型”异常。在这篇文章中,我们将深入探讨这种异常的可能原因,并探索解决方案。

2. 忘记添加@Entity注解

遇到此异常的一个可能原因是我们在实体类上忘记使用 @Entity注解

2.1. 复现问题

假设我们有以下实体类:

public class EntityWithoutAnnotation {
    @Id
    private Long id;
}

我们还有其Spring Data JPA的仓库:

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");
}

正如预期,我们遇到了与我们的实体相关的“非受管理类型”异常。

2.2. 解决问题

让我们将@Entity注解添加到修复后的实体版本:

@Entity
public class EntityWithoutAnnotationFixed {
    @Id
    private Long id;
}

应用和仓库类保持不变。再次尝试启动应用:

@Test
void givenEntityWithoutAnnotationApplicationFixed_whenBootstrap_thenRepositoryBeanShouldBePresentInContext() {
    ConfigurableApplicationContext context = run(EntityWithoutAnnotationFixedApplication.class);
    EntityWithoutAnnotationFixedRepository repository = context
      .getBean(EntityWithoutAnnotationFixedRepository.class);

    assertThat(repository).isNotNull();
}

我们成功获取了ConfigurableApplicationContext实例,并从此处获得了仓库实例。

3. 从javax.persistence迁移到jakarta.persistence

当我们的应用迁移至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。我们将按照上一节创建仓库和应用类的方式进行创建。

现在尝试启动应用:

@Test
void givenEntityWithJakartaAnnotationApplication_whenBootstrap_thenExpectedExceptionThrown() {
    Exception exception = assertThrows(Exception.class,
      () -> run(EntityWithJakartaAnnotationApplication.class));

    assertThat(exception)
      .getRootCause()
      .hasMessageContaining("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

另一个可能导致“非受管理类型”异常的常见情况是当我们的JPA实体扫描器无法在预期路径中找到实体时

4.1. 复现问题

首先,我们创建另一个实体:

package com.baeldung.spring.entity;
@Entity
public class CorrectEntity {
    @Id
    private Long id;
}

它带有@Entity注解,并放在entity包中。现在,我们创建一个仓库:

package com.baeldung.spring.repository;

public interface CorrectEntityRepository extends JpaRepository<CorrectEntity, Long> {

}

我们的仓库位于repository包中。最后,我们创建一个应用类:

package com.baeldung.spring.app;

@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.baeldung.spring.repository")
public class WrongEntityScanApplication {

}

它位于app包中。默认情况下,Spring Data会在主类包及其子包下查找仓库。因此,我们需要使用@EnableJpaRepositories注解指定基础仓库包。现在尝试启动应用:

@Test
void givenWrongEntityScanApplication_whenBootstrap_thenExpectedExceptionThrown() {
    Exception exception = assertThrows(Exception.class,
      () -> run(WrongEntityScanApplication.class));

    assertThat(exception)
      .getRootCause()
      .hasMessageContaining("Not a managed type");
}

我们再次遇到了“非受管理类型”异常。原因在于实体扫描逻辑与仓库扫描相同。在我们的app包下的包中进行了扫描,但没有在那里找到任何实体,从而在构建CorrectEntityRepository时出现了异常。

4.2. 解决问题

要解决这个问题,我们可以使用**@EntityScan注解**。

让我们创建另一个应用类:

@SpringBootApplication
@EnableJpaRepositories(basePackages =
  "com.baeldung.spring.repository")
@EntityScan("com.baeldung.spring.entity")
public class WrongEntityScanFixedApplication {

}

现在,我们使用@EnableJpaRepositories注解指定仓库包,并使用@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时为何可能会遇到“非受管理类型”异常,以及如何避免它。解决方案的选择取决于具体的情况,但理解可能的原因有助于我们识别我们所面对的具体变种。

如往常一样,完整的源代码可以在GitHub上找到。