new SingleEntityExecution();
如上述代码所示,根据method变量实例化时的查询设置方式,实例化不同的JpaQueryExecution子类实例去运行。我们的findByUserName
最终落入了SingleEntityExecution
—— 返回单个实例的 Execution
。继续跟踪execute
方法,发现底层使用了 hibernate 的 CriteriaQueryImpl
完成了sql的拼装,这里就不做赘述了。
再来看看这类的method
。在 spring-data-jpa 中,JpaQueryMethod
就是Repository
接口中带有@Query
注解方法的全部信息,包括注解,类名,实参等的存储类,所以Repository
接口有多少个@Query
注解方法,就会包含多少个JpaQueryMethod
实例被加入监听序列。实际运行时,一个RepositoryQuery
实例持有一个JpaQueryMethod
实例,JpaQueryMethod
又持有一个Method
实例。
再来看看RepositoryQuery
,在QueryExecutorMethodInterceptor
中维护了一个Map<Method, RepositoryQuery> queries
。RepositoryQuery
的直接抽象子类是AbstractJpaQuery
,可以看到,一个RepositoryQuery
实例持有一个JpaQueryMethod
实例,JpaQueryMethod
又持有一个Method
实例,所以RepositoryQuery
实例的用途很明显,一个RepositoryQuery
代表了Repository
接口中的一个方法,根据方法头上注解不同的形态,将每个Repository
接口中的方法分别映射成相对应的RepositoryQuery
实例。
下面我们就来看看spring-data-jpa对RepositoryQuery实例的具体分类:
1.SimpleJpaQuery
方法头上@Query注解的nativeQuery属性缺省值为false,也就是使用JPQL,此时会创建SimpleJpaQuery实例,并通过两个StringQuery类实例分别持有query jpql语句和根据query jpql计算拼接出来的countQuery jpql语句;
2.NativeJpaQuery
方法头上@Query注解的nativeQuery属性如果显式的设置为nativeQuery=true,也就是使用原生SQL,此时就会创建NativeJpaQuery实例;
3.PartTreeJpaQuery
方法头上未进行@Query注解,将使用spring-data-jpa独创的方法名识别的方式进行sql语句拼接,此时在spring-data-jpa内部就会创建一个PartTreeJpaQuery实例;
4.NamedQuery
使用javax.persistence.NamedQuery注解访问数据库的形式,此时在spring-data-jpa内部就会根据此注解选择创建一个NamedQuery实例;
5.StoredProcedureJpaQuery
顾名思义,在Repository接口的方法头上使用org.springframework.data.jpa.repository.query.Procedure注解,也就是调用存储过程的方式访问数据库,此时在spring-data-jpa内部就会根据@Procedure注解而选择创建一个StoredProcedureJpaQuery实例。
那么问题来了,sql 拼接的时候怎么知道是根据userName
进行查询呢?是取自方法名中的 byUsername 还是方法参数 userName 呢? spring 具体是在什么时候知道查询参数的呢 ?
数据如何注入
spring 在启动的时候会实例化一个 Repositories,它会去扫描所有的 class,然后找出由我们定义的、继承自org.springframework.data.repository.Repositor
的接口,然后遍历这些接口,针对每个接口依次创建如下几个实例:
SimpleJpaRespositry
—— 用来进行默认的 DAO 操作,是所有 Repository 的默认实现
JpaRepositoryFactoryBean
—— 装配 bean,装载了动态代理 Proxy,会以对应的 DAO 的 beanName 为 key 注册到DefaultListableBeanFactory
中,在需要被注入的时候从这个 bean 中取出对应的动态代理 Proxy 注入给 DAO
JdkDynamicAopProxy
—— 动态代理对应的InvocationHandler
,负责拦截 DAO 接口的所有的方法调用,然后做相应处理,比如findByUsername
被调用的时候会先经过这个类的 invoke 方法
在JpaRepositoryFactoryBean.getRepository()
方法被调用的过程中,还是在实例化QueryExecutorMethodInterceptor
这个拦截器的时候,spring 会去为我们的方法创建一个PartTreeJpaQuery
,在它的构造方法中同时会实例化一个PartTree
对象。PartTree
定义了一系列的正则表达式,全部用于截取方法名,通过方法名来分解查询的条件,排序方式,查询结果等等,这个分解的步骤是在进程启动时加载 Bean 的过程中进行的,当执行查询的时候直接取方法对应的PartTree
用来进行 sql 的拼装,然后进行 DB 的查询,返回结果。
到此为止,我们整个JpaRepository
接口相关的链路就算走通啦,简单的总结如下:
spring 会在启动的时候扫描所有继承自 Repository 接口的 DAO 接口,然后为其实例化一个动态代理,同时根据它的方法名、参数等为其装配一系列DB操作组件,在需要注入的时候为对应的接口注入这个动态代理,在 DAO 方法被调用的时会走这个动态代理,然后经过一系列的方法拦截路由到最终的 DB 操作执行器JpaQueryExecution
,然后拼装 sql,执行相关操作,返回结果。
基本查询分为两种,一种是 spring data 默认已经实现(只要继承JpaRepository
),一种是根据查询的方法来自动解析成 SQL。
public interface UserRepository extends JpaRepository<User, Long> {
@Test
public void testBaseQuery() throws Exception {
User user=new User();
userRepository.findAll();
userRepository.findOne(1l);
userRepository.save(user);
userRepository.delete(user);
userRepository.count();
userRepository.exists(1l);