Spring Data JPA 异常 “IllegalArgumentException: Not a Managed Type”

1、概览

使用 Spring Data JPA 时,应用启动出现异常。大致如下:

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

大意是说,一些 Bean 创建失败了,导致应用启动失败。

根异常是 IllegalArgumentException:“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> {
}

最后是 Application 启动类,它会扫描上面定义的所有类:

@SpringBootApplication
public class EntityWithoutAnnotationApplication {

}

尝试使用此 Application 来启动 Spring Context:

@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;
}

Application 和 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 Persistence 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 和 Application 类。

现在,尝试启动应用:

@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 实体扫描器(Entity Scanner)希望我们使用 javax.persistence.Entity 注解,而不是 jakarta.persistence.Entity 注解。

3.2、修复问题

在这种情况下,有两种可能的解决方案。

  • 升级到 Spring Boot 3,然后使用 jakarta.persistence
  • 如果还没准备好升级,就继续使用 javax.persistence.Entity

4、缺少或错误配置了 @EntityScan

另一种可能遇到 “Not a managed type” 异常的常见情况是,JPA 实体扫描器(Entity Scanner)无法在预期路径中找到实体。

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 包中。最后,创建一个 Application 类:

package com.baeldung.spring.app;

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

}

它在 app 包中。默认情况下,Spring Data 会扫描 main 类所在包及其子包下的 Repository 接口。因此,我们需要使用 @EnableJpaRepositoriesbasePackages 属性指定 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 注解。

再创建一个 Application 类:

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

}

如上,使用 @EnableJpaRepositories 注解指定 Repository 所在包,并使用 @EntityScan 注解指定实体包。

执行测试:

@Test
void givenWrongEntityScanApplicationFixed_whenBootstrap_thenRepositoryBeanShouldBePresentInContext() {
    ConfigurableApplicationContext context = run(WrongEntityScanFixedApplication.class);
    CorrectEntityRepository repository = context
      .getBean(CorrectEntityRepository.class);

    assertThat(repository).isNotNull();
}

测试通过。我们从 Context 中成功获取到了 CorrectEntityRepository,并且 CorrectEntity 已被成功识别为 JPA 实体。

5、总结

本文介绍了在使用 Spring Data JPA 时出现 “Not a managed type” 异常的各种原因,以及对应的解决办法。


Ref:https://www.baeldung.com/spring-data-jpa-not-managed-type-exception