spring-bootjpaspring-data-jpaaspectjload-time-weaving

Could not safely identify store assignment for repository candidate interface even when the entity is annotated with jakarta.persistence.Entity


I'm trying to enable LoadTimeWeaving in spring boot project.

What I've done so far:

  1. Added @EnableLoadTimeWeaving annotation
  2. Added vm options when running the application (-javaagent:path/to/spring-instrument.jar)

Upon starting the application I'm getting the following error:

13:27:55.492 [main] INFO  o.s.data.repository.config.RepositoryConfigurationExtensionSupport - Spring Data JPA - Could not safely identify store assignment for repository candidate interface com.something.SomethingRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository

image

Notes:

  1. Annotated my entity with @jakarta.persistence.Entity
  2. I am extending org.springframework.data.repository.CrudRepository here. It seems to work if I use JpaRepository instead. However, I would like to know the root cause of this. Shouldn't annotating the entity with @Entity be enough as stated in the error? I think I'm missing something here.

Observations:

  1. org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension#getIdentifyingAnnotations gets Entity.class, and MappedSuperClass.class from class loader ClassLoaders.AppClassLoader.
@Override
    protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
        return Arrays.asList(Entity.class, MappedSuperclass.class);
    }
  1. This is then compared with the @Entity annotation from my entity to see if they are the same by org.springframework.core.annotation.TypeMappedAnnotations#isMappingForType method. But they are not equal probably because of different class loader. More on this in #3
    private static boolean isMappingForType(AnnotationTypeMapping mapping,
            AnnotationFilter annotationFilter, @Nullable Object requiredType) {

        Class<? extends Annotation> actualType = mapping.getAnnotationType();
        return (!annotationFilter.matches(actualType) &&
                (requiredType == null || actualType == requiredType || actualType.getName().equals(requiredType)));
    }
  1. When getting the class loader to get the repository interfaces org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension#getConfigurationInspectionClassLoader method is invoked which returns InspectionClassLoader as LazyJvmAgent.isActive(loader.getClassLoader()) returns true.
    @Override
    protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) {

        ClassLoader classLoader = loader.getClassLoader();

        return classLoader != null && LazyJvmAgent.isActive(loader.getClassLoader())
                ? new InspectionClassLoader(loader.getClassLoader())
                : loader.getClassLoader();
    }

So my question is: What is it that I'm missing here? Shouldn't they use the same class loader if we need to check equality like that? I would change all CrudRepository to JpaRepository but it might create some other issues it kind of looks hackish to me as it should already work with @Entity annotation. Any insights on this?

Edit: Note: I have multiple data source in this project.


Solution

  • The problem is simply that spring-instrument is an agent which attaches another agent - namely aspectjweaver - later during runtime. Invesdwin does a similar thing, trying to avoid the java agent on the command line altogether.

    The solution is to simply do it right and use -javaagent:/path/to/aspectjweaver-1.9.22.jar (the version used by your sample program) directly instead of spring-instrument. I tried with your sample, changing one line in your build.gradle as follows:

    jvmArgs(["-javaagent:/path/to/aspectjweaver-1.9.22.jar"])
    

    It works beautifully. @Entity and CrudRepository are enough, just as you like it. The agent needs to be attached during the start of the JVM, then there are no classloader problems.

    BTW, if you want to embed the AspectJ weaver or any other agent into an executable Spring Boot JAR and auto-start it from there without any extra command line arguments, take a look at Agent Embedder Maven Plugin. I am the maintainer, and no, I did not port it to Gradle.