To repro, put a debugger in org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepository(java.lang.Class<T>, org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments)
Includes parsing the magical method name syntax
Repos are registered as beans on app boot, then their proxies are created lazily when you first autowire the repo.
Spring boot app with this repo and application class.
// The one repo
@Repository
public interface BookRepository extends PagingAndSortingRepository<Book, UUID> {
List<Book> findAllByTitle(String title);
}
// Application class
@Autowired
BookRepository bookRepository;
public static void main(String[] args) {
SpringApplication.run(DatarestdemoApplication.class, args);
}JpaRepositoriesAutoConfigurationfor spring boot importsJpaRepositoriesRegistrar- This adds the
@EnableJpaRepositoriesannotation to the app config - and it imports
JpaRepositoriesRegistrar.classNote
JpaRepositoriesRegistrarextendsAbstractRepositoryConfigurationSourceSupportwhich implementsImportBeanDefinitionRegistrarwhich is "Interface to be implemented by types that register additional bean definitions when processing Configuration classes" - on boot, spring comes around to do its
@Configurationbased bean creation (out of scope) JpaRepositoriesRegistraris found andregisterBeanDefinitions(in parent class) is called, which calls:org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesInJpaRepositoriesRegistrar looks like
AbstractRepositoryConfigurationSourceSupportin the debugger, but that's an abstract class. If you checkthisyou'll see it.- the
JpaRepositoriesRegistrarfinds each repository and registers it with the root beanJpaRepositoryFactoryBean - That bean has an after properties set hook (which bean registration picks up)
org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#afterPropertiesSet- Just sets up some stuff that we don’t really care about (afaik)
-
spring gets the autowire request
-
Lazy inits a
org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(java.lang.Class<T>, org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments) -
That sets up a AOP proxy programmatically
ProxyFactory result = new ProxyFactory(); result.setTarget(target); result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
note how there are 3 interfaces set up
-
Adds a bunch of advices
CrudMethodMetadataPopulatingMethodInterceptor PersistenceExceptionTranslationInterceptor TransactionInterceptor QueryExecutorMethodInterceptor ImplementationMethodExecutionInterceptor
see below section for how this builds queries
-
QueryExecutorMethodInterceptorconstructor does its work:- Constructor sets
this.queries, by callingmapMethodsToQuery - if you follow that all the way down, you end up in
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.DeclaredQueryLookupStrategy.resolveQuery - branch 1 tries to resolve by @Query annotation, then @Procedure annotation, then NamedQuery. It throws
IllegalStateExceptionbecause none exist. - branch 2 (when branch 1 throws) is caught in here
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.CreateIfNotFoundQueryLookupStrategy.createStrategy - that builds a
PartTreeJpaQuerywhich constructs a neworg.springframework.data.repository.query.parser.PartTree - this parses by a regex matcher, splitting subject "findAllBy", from predicate "Title".
- The
org.springframework.data.repository.query.parser.PartTree.Predicateconstructor splits the nodes of the predicate part. (if you had TitleAndAuthorFirstName, this would split Title and AuthorFirstName.) - it first splits on "or", then passes each "or" group to the
org.springframework.data.repository.query.parser.PartTree.OrPart - that looks for ands and collects all the parts
- all of this is worked through
org.springframework.data.jpa.repository.query.JpaQueryCreator.JpaQueryCreatorand returned back to step 2 (resolveQuery)
- Constructor sets
-
That uses type casting to get a proxy instance
T repository = (T) result.getProxy(classLoader);in this case, the type is org.springframework.data.jpa.repository.support.SimpleJpaRepository
Keep in mind, the repo itself is a proxy instance which includes an advice for
QueryExecutorMethodInterceptor
Put a debugger in org.springframework.data.repository.core.support.RepositoryFactorySupport.QueryExecutorMethodInterceptor.invoke to repro this
Now go run the app and trigger one of the named queries like findAllByName()
- It gets the method from the proxy invocation, which goes through all the other proxies, eventually to the
QueryExecutorMethodInterceptor org.springframework.data.repository.core.support.RepositoryFactorySupport.QueryExecutorMethodInterceptor.doInvokepulls the query from the queries object that was set up aboveorg.springframework.data.jpa.repository.query.JpaQueryExecution.execute- you can trace that down to
org.springframework.data.jpa.repository.query.PartTreeJpaQuery.QueryPreparer.createQuery(org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor)... org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecutequery.createQuery(accessor).getResultList();2. we jump through a bunch of methods called createQuery() 3. then we invoke method calledcreateQueryon the entity manager akaSessionImpl(hibernate session))- the type of the query is
CriteriaQueryTypeQueryAdapterwhich is a JPA wrapper in hibernate
- the type of the query is
getResultList()is hibernate code! https://www.objectdb.com/java/jpa/query/execute
Fin