spring-projects / spring-data-jpa

Simplifies the development of creating a JPA-based data access layer.
https://spring.io/projects/spring-data-jpa/
Apache License 2.0
2.98k stars 1.41k forks source link

java.lang.IllegalStateException is thrown by invoking findBy method #3294

Open quaff opened 8 months ago

quaff commented 8 months ago
java.lang.IllegalStateException: No MethodInvocation found: Check that an AOP invocation is in progress, and that the CrudMethodMetadataPopulatingMethodInterceptor is upfront in the interceptor chain.
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.currentInvocation(CrudMethodMetadataPostProcessor.java:123)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$ThreadBoundTargetSource.getTarget(CrudMethodMetadataPostProcessor.java:294)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:229)
    at jdk.proxy2/jdk.proxy2.$Proxy100.getLockModeType(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applyRepositoryMethodMetadata(SimpleJpaRepository.java:845)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:756)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.lambda$doFindBy$2(SimpleJpaRepository.java:521)
    at org.springframework.data.jpa.repository.support.FetchableFluentQueryBySpecification.createSortedAndProjectedQuery(FetchableFluentQueryBySpecification.java:183)
    at org.springframework.data.jpa.repository.support.FetchableFluentQueryBySpecification.stream(FetchableFluentQueryBySpecification.java:166)
    at findby.FindByTests.findBy(FindByTests.java:30)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

Here is test case:

package findby;

import java.util.function.Function;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ContextConfiguration;

import jakarta.persistence.Entity;

@DataJpaTest
@EnableJpaRepositories(basePackageClasses = FindByTests.TestEntityRepository.class, considerNestedRepositories = true)
@EntityScan(basePackageClasses = FindByTests.TestEntity.class)
@ContextConfiguration(classes = FindByTests.class)
class FindByTests {

    @Autowired
    TestEntityRepository repository;

    @Test
    void findBy() {
        // will throw java.lang.IllegalStateException: No MethodInvocation found
        repository.findBy((root, query, cb) -> null, Function.identity()).stream();
    }

    @Test
    void findByInDefaultMethod() {
        // works fine
        repository.findByInDefaultMethod();
    }

    interface TestEntityRepository extends JpaRepository<TestEntity, Long>, JpaSpecificationExecutor<TestEntity> {

        default Stream<TestEntity> findByInDefaultMethod() {
            return findBy((root, query, cb) -> null, Function.identity()).stream();
        }

    }

    @Entity
    static class TestEntity extends AbstractPersistable<Long> {

    }

}

with

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version 'latest.release'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'com.h2database:h2'
}

tasks.named('test') {
    useJUnitPlatform()
}
pbeltechi commented 8 months ago

I also ecountered this using "org.springframework.boot:spring-boot-starter-data-mongodb"

christophstrobl commented 8 months ago

Thank you @quaff for reporting and adding the test snippet. The current behaviour, though unintuitive, is the expected one. The findBy method in this case exposes the FetchableFluentQuery outside the repository. So when the stream invocation happens the context is already gone.

We're considering to enhance the current flow so that we capture method metadata early and can provide it later on in this scenario.

Meanwhile you can try it this way repository.findBy((root, query, cb) -> null, FetchableFluentQuery::stream).

quaff commented 8 months ago

Meanwhile you can try it this way repository.findBy((root, query, cb) -> null, FetchableFluentQuery::stream).

Thanks, it works, does the document mention such limitation?

christophstrobl commented 8 months ago

Unfortunately not, all samples are using one of the terminating (one, first, all,...) methods.