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);
}
JpaRepositoriesAutoConfiguration
for spring boot importsJpaRepositoriesRegistrar
- This adds the
@EnableJpaRepositories
annotation to the app config - and it imports
JpaRepositoriesRegistrar.class
Note
JpaRepositoriesRegistrar
extendsAbstractRepositoryConfigurationSourceSupport
which implementsImportBeanDefinitionRegistrar
which is "Interface to be implemented by types that register additional bean definitions when processing Configuration classes" - on boot, spring comes around to do its
@Configuration
based bean creation (out of scope) JpaRepositoriesRegistrar
is found andregisterBeanDefinitions
(in parent class) is called, which calls:org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesIn
JpaRepositoriesRegistrar looks like
AbstractRepositoryConfigurationSourceSupport
in the debugger, but that's an abstract class. If you checkthis
you'll see it.- the
JpaRepositoriesRegistrar
finds 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
-
QueryExecutorMethodInterceptor
constructor 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
IllegalStateException
because none exist. - branch 2 (when branch 1 throws) is caught in here
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.CreateIfNotFoundQueryLookupStrategy.createStrategy
- that builds a
PartTreeJpaQuery
which 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.Predicate
constructor 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.JpaQueryCreator
and 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.doInvoke
pulls 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.doExecute
query.createQuery(accessor).getResultList();
2. we jump through a bunch of methods called createQuery() 3. then we invoke method calledcreateQuery
on the entity manager akaSessionImpl
(hibernate session))- the type of the query is
CriteriaQueryTypeQueryAdapter
which 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