spring-data-jpa-entity-graph icon indicating copy to clipboard operation
spring-data-jpa-entity-graph copied to clipboard

Errors during entity graph call to DB after upgrading Spring boot/spring-data-jpa-entity-graph

Open mr-nothing opened this issue 2 years ago • 1 comments

What steps will reproduce the problem ?

I'm not sure if there is an easy way to reproduce the issue since I don't face it in a local environment but this error always come up in k8s environment. Here are probable steps that cause it:

Steps to reproduce the behaviour:

  1. Try to fetch entities by id with method findAllById (probably with a long list of ids, but not sure if length of the list matters) in repository component with defaultEntityGraph overridden and EntityGraphJpaRepository interface implemented in async manner. To be more precise I use Netflix DGS and it's entity fetchers which use ForkJoinPools under the hood to parallel requests to backend services, as far as I know.

What is the expected output ?

The request to database is executed with no issues.

What happens instead ?

The error is faced:

j.l.IllegalArgumentException: org.hibernate.query.hql.spi.SqmQueryImplementor referenced from a method is not visible from class loader
at j.l.r.Proxy$ProxyBuilder.ensureVisible(Unknown Source)
at j.l.r.Proxy$ProxyBuilder.validateProxyInterfaces(Unknown Source)
at j.l.r.Proxy$ProxyBuilder.<init>(Unknown Source)
at j.lang.reflect.Proxy.lambda$getProxyConstructor$1(Unknown Source)
at j.i.l.AbstractClassLoaderValue$Memoizer.get(Unknown Source)
at j.i.l.AbstractClassLoaderValue.computeIfAbsent(Unknown Source)
at j.lang.reflect.Proxy.getProxyConstructor(Unknown Source)
at j.lang.reflect.Proxy.newProxyInstance(Unknown Source)
at o.s.a.f.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123)
at o.s.a.f.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:115)
at o.s.a.f.ProxyFactory.getProxy(ProxyFactory.java:97)
at c.c.s.d.j.e.g.r.s.RepositoryQueryEntityGraphInjector.proxy(RepositoryQueryEntityGraphInjector.java:37)
at c.c.s.d.j.e.g.r.s.RepositoryEntityManagerEntityGraphInjector.invoke(RepositoryEntityManagerEntityGraphInjector.java:70)
at o.s.a.f.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at o.s.a.f.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244)
at j.proxy2.$Proxy218.createQuery(Unknown Source)
at o.s.d.j.r.s.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:755)
at o.s.d.j.r.s.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:733)
at o.s.d.j.r.s.SimpleJpaRepository.findAllById(SimpleJpaRepository.java:408)
at j.i.r.NativeMethodAccessorImpl.invoke0(Unknown Source)
at j.i.r.NativeMethodAccessorImpl.invoke(Unknown Source)
at j.i.r.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at j.l.reflect.Method.invoke(Unknown Source)
at o.s.d.r.c.s.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:288)
at o.s.d.r.c.s.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
at o.s.d.r.c.s.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
at o.s.d.r.c.s.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
at o.s.d.r.c.s.RepositoryComposition.invoke(RepositoryComposition.java:285)
at o.s.d.r.c.s.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
at o.s.a.f.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
... 10 frames truncated
... 39 common frames omitted\nWrapped by: o.s.d.InvalidDataAccessApiUsageException: org.hibernate.query.hql.spi.SqmQueryImplementor referenced from a method is not visible from class loader
at o.s.o.j.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371)
at o.s.o.j.v.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:234)
at o.s.o.j.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
at o.s.d.s.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at o.s.d.s.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at o.s.d.s.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at o.s.a.f.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at c.c.s.d.j.e.g.r.s.RepositoryMethodInvocation.proceed(RepositoryMethodInvocation.java:23)
at c.c.s.d.j.e.g.r.s.EntityGraphQueryHintCandidates.doInvoke(EntityGraphQueryHintCandidates.java:96)
at c.c.s.d.j.e.g.r.s.EntityGraphQueryHintCandidates.invoke(EntityGraphQueryHintCandidates.java:60)
at o.s.a.f.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at o.s.d.j.r.s.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164)
at o.s.a.f.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at o.s.a.i.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at o.s.a.f.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)

Environment

  • Spring Data JPA version (not the Spring Boot one): 3.1.2
  • ORM with version:
    • org.hibernate.common:hibernate-commons-annotations:6.0.6.Final
    • org.hibernate.orm:hibernate-core:6.2.7.Final
    • org.hibernate.validator:hibernate-validator:8.0.1.Final
  • spring-data-jpa-entity-graph version: 3.1.0

Link to an automated test demonstrating the problem

No luck to create a test or provide environment to reproduce it.

Additional context

I talked with guys from spring boot and they gave me some places worth to look at: https://github.com/spring-projects/spring-boot/issues/37474

mr-nothing avatar Sep 20 '23 12:09 mr-nothing

Hey, just an FYI as this came up for myself. Its not kubernetes, its within a docker container, so I was able to recreate this in an unrelated project locally.

I believe this is fixed by the following answer on stackoverflow:

https://stackoverflow.com/a/59444016

which in turn depends on the following solution in creating a ForkJoinerWorkerThreadFactory and propagating the correct class loader to the other thread.

https://stackoverflow.com/questions/49113207/completablefuture-forkjoinpool-set-class-loader/57551188#57551188

public class MyForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {

    @Override
    public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        return new MyForkJoinWorkerThread(pool);
    }

    private static class MyForkJoinWorkerThread extends ForkJoinWorkerThread {

        private MyForkJoinWorkerThread(final ForkJoinPool pool) {
            super(pool);
            // set the correct classloader here
            setContextClassLoader(Thread.currentThread().getContextClassLoader());
        }
    }
}

Delcare custom executor:

@Bean(name = "customExecutor")
    public Executor customExecutor() {
        MyForkJoinWorkerThreadFactory factory = new MyForkJoinWorkerThreadFactory();
        ForkJoinPool myCommonPool = new ForkJoinPool(Math.min(32767, Runtime.getRuntime().availableProcessors()), factory, null, false);

        return new DelegatingSecurityContextExecutor(myCommonPool, SecurityContextHolder.getContext());
    }

At that point the usage of data loader should use the customExecutor bean

 @Bean(name = "myDataLoader")
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public DataLoader<Object, Object> myDataLoader(@Qualifier("customExecutor") Executor customExecutor) {
return DataLoaderFactory.newDataLoader(myValue -> {

CompletableFuture.supplyAsync(() -> {
                return 1L; // perform the actual work causing the exception here
            }, customExecutor));
}):

SergeantGrumbles avatar Jan 20 '25 11:01 SergeantGrumbles