springbootJava后端Web基础

spring data jpa 工作流程及原理

2018-10-25  本文已影响289人  打不开的回忆

最近帮同事看jpa的查询问题,抽空整理了一下相关知识,本篇先介绍jpa的工作流程(原理),后续再写关于实战以及开发中常用到的各种查询。本文是基于debug源码结合自己的理解所写,可能有些细节被漏掉,也可能对某一块解释的不详细,具体的知识点建议结合本文,对源码进行阅读。也希望各位大佬留言斧正!

尝鲜

导入jpa相关jar(maven请自行搜索)
dependencies {
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    runtimeOnly('mysql:mysql-connector-java')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}
定义Entity和Repository以及Test测试类
#entity类
@Entity(name = "Customer")
public class Customer {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
  private String email;
  private String address;
  private String phone;
    ...setter getter 省略
}
#repository类
public interface CustomerRepository extends JpaRepository<Customer,Long> {

  List<Customer> findCustomerByAddress(String address);

  @Query("from Customer customer")
  List<Customer> getCustomer();
}
#测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaApplicationTests {
  @Autowired
  private CustomerRepository customerRepository;

  @Test
  public void contextLoads() {
    customerRepository.findCustomerByAddress("aa");
    customerRepository.findAll();
  }
}

正餐

启动过程

由于引入了starter-data-jpa,自动配置,spring启动时会实例化一个Repositories,然后扫描包,找出所有继承Repository的接口(除@NoRepositoryBean注解的类),遍历装配。为每一个接口创建相关实例。

  1. SimpleJpaRespositry——用来进行默认的DAO操作,是所有Repository的默认实现
  2. JpaRepositoryFactoryBean——装配bean,装载了动态代理Proxy,会以对应的DAO的beanName为key注册到DefaultListableBeanFactory中,在需要被注入的时候从这个bean中取出对应的动态代理Proxy注入给DAO
  3. JdkDynamicAopProxy——动态代理对应的InvocationHandler,负责拦截DAO接口的所有的方法调用,然后做相应处理,比如findByUsername被调用的时候会先经过这个类的invoke方法
    关于启动过程的初始化,始终没有整理太明白,奉上一张大佬的时序图,有兴趣的可以自己研究:(图片来源:(http://www.luckyzz.com/java/spring-data-jpa/)) proxy2 (1).png
debug源码
断点查看customerRepository,发现该接口被注入他的实现类作为代理对象,基于JDK的动态代理JdkDynamicAopProxy执行invoke方法,继续跟进,查看拦截链: chain

经过这里系列的拦截器的方法后,查询的拦截器有图中的QueryExecutorMethodInterceptor和ImplementMethodInterceptor(均位于RepositoryFactorySupport内部),个人理解是repository默认的函数走后者,自定义查询走前者。
继续跟进,进入QueryExecutorMethodInterceptor的invoke方法,执行doInvoke,对方法进行判断,是否自定查询,这里我们是自定义的查询,继续看。

@Nullable
        private Object doInvoke(MethodInvocation invocation) throws Throwable {

            Method method = invocation.getMethod();
            Object[] arguments = invocation.getArguments();

            if (hasQueryFor(method)) {
                return queries.get(method).execute(arguments);
            }

            return invocation.proceed();
        }

由于不会画时序图,根据自己的理解,简单瞎画了一张图,帮助理清楚思路。(画了之后确实清楚一些)希望有助于阅读理解。


jpa.png

这里前面说的初始化RepositoryFactorySupport时已经扫描所有repository,并未其选择了query策略,这里是PartTreeJpaQuery。


PartTreeJpaQuery

执行PartTreeJpaQuery.execute方法(该类继承了AbstracJpaQuery),

public Object execute(Object[] parameters) {
        return doExecute(getExecution(), parameters);
    }
private Object doExecute(JpaQueryExecution execution, Object[] values) {

        Object result = execution.execute(this, values);

        ParametersParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), values);
        ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);

        return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType()));
    }
protected JpaQueryExecution getExecution() {
        if (method.isStreamQuery()) {
            return new StreamExecution();
        } else if (method.isProcedureQuery()) {
            return new ProcedureExecution();
        } else if (method.isCollectionQuery()) {
            return new CollectionExecution();
        } else if (method.isSliceQuery()) {
            return new SlicedExecution(method.getParameters());
        } else if (method.isPageQuery()) {
            return new PagedExecution(method.getParameters());
        } else if (method.isModifyingQuery()) {
            return new ModifyingExecution(method, em);
        } else {
            return new SingleEntityExecution();
        }
    }

更加方法的返回值,入参,注解等等条件判断,得到一个query执行器(JpaQueryExecution)CollectionExecution,进入该execution查看其execute方法:

#JpaQueryExecution.java
public Object execute(AbstractJpaQuery query, Object[] values) {

        Assert.notNull(query, "AbstractJpaQuery must not be null!");
        Assert.notNull(values, "Values must not be null!");

        Object result;

        try {
            result = doExecute(query, values);
        } catch (NoResultException e) {
            return null;
        }
          ..........
    }
static class CollectionExecution extends JpaQueryExecution {

        @Override
        protected Object doExecute(AbstractJpaQuery query, Object[] values) {
            return query.createQuery(values).getResultList();
        }
    }

看到这是不是熟悉了,原来还是创建一个Query然后getResultList获取结果,使用jpa自定执行sql时,我们可以这么操作:EntityManager.createQuery("sql",ReturnClass),我们继续看看这个PartTreeJpaQuery是怎么创建Query的。
这里先解释一下为什么初始化时会为findCustomerByAddress这个方法注入PartTreeJpaQuery,查看AbstractJpaQuery抽象类,其有五个子类实现,

#PartTreeJpaQuery.java
public Query createQuery(Object[] values) {

            CriteriaQuery<?> criteriaQuery = cachedCriteriaQuery;
            ParameterBinder parameterBinder = cachedParameterBinder;
            ParametersParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);

            if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) {
                JpaQueryCreator creator = createCreator(persistenceProvider, Optional.of(accessor));
                criteriaQuery = creator.createQuery(getDynamicSort(values));
                List<ParameterMetadata<?>> expressions = creator.getParameterExpressions();
                parameterBinder = getBinder(expressions);
            }

            if (parameterBinder == null) {
                throw new IllegalStateException("ParameterBinder is null!");
            }

            return restrictMaxResultsIfNecessary(invokeBinding(parameterBinder, createQuery(criteriaQuery), values));
        }

private TypedQuery<?> createQuery(CriteriaQuery<?> criteriaQuery) {

            if (this.cachedCriteriaQuery != null) {
                synchronized (this.cachedCriteriaQuery) {
                    return getEntityManager().createQuery(criteriaQuery);
                }
            }

            return getEntityManager().createQuery(criteriaQuery);
        }

PartTreeJpaQuery源码如上,包含参数绑定,解析等等,利用JpaQueryCreator创建一个CriteriaQuery,丢进重载方法createQuery方法中,返回一个Query。注意查看PartTreeJpaQuery的构造函数,其中会为其定义一个PartTree对象,PartTree对象包含了sql关键字的正则解析。创建JpaQueryCreator时会传入该PartTree,CriteriaQuery就这么生成了。最后一步在重载方法里看到了entityManager.createQuery(),一切明了了。至于PartTree如何工作的,不再研究!

甜点

repository的默认方法,使用ImplementMethodInterceptor拦截器,会对每个默认方法单独实现,断点进走完拦截链后,直接进去SimpleJpaRepository的各自方法了,其中也是getQuery(),然后执行execute()方法获取的,这里不再分析。有兴趣的可以继续查看源码!

回味

导入jpa,自动配置扫描包内所有继承了Repository接口的接口,为每一个接口实例一个代理类(SimpleJpaRepository),并为每个方法确定一个query策略,已经其他所需的bean,使用自定的repository时,是使用的代理类,经过一些列的拦截器后,选取一个query执行器JpaQueryExecution,然后创建Query对象,拼接sql,组装参数等DB操作,最后返回Query.getResultList()。
基本流程就是这么多,当然对于如何初始化的,还没理清楚,即初始化时如何为每个方法分配具体的query策略,除了PartTreeJpaQuery外的其他query策略如何工作,PartTree如何解析sql等等没有深入研究。
ps:下一篇介绍开发中遇到的实际操作问题。
本文只梳理源码流程,并未深入研究,如有不正之处请联系作者进行修改,万分感谢!

微信: 728961272908841360.jpg
上一篇下一篇

猜你喜欢

热点阅读