概述

当我们使用 Spring Data JPA 的时候,典型的用法是这样的 :

  1. spring-data-jpa 包,数据库驱动包等添加为项目依赖;
  2. 配置文件定义相应的数据源;
  3. 为应用添加注解@EnableJpaRepositories;
  4. 定义业务领域实体类,比如通过@Entity注解;
  5. 定义自己业务相关的的JPA repository接口,这些接口都继承自JpaRepository或者JpaSpecificationExecutor;
  6. 将上面自定义JPA repository接口注入到服务层并使用它们进行相应的增删改查;

在这个流程中,我们并没有对自定义JPA repository接口提供任何实现,但神奇的是在服务层我们居然可以成功地注入该接口类型的bean并使用。那么,问题来了,这个bean到底是个什么东西呢,又是为什么能够成功注入呢 ?这篇文章我们基于Springboot应用模式和Spring的源代码试图回答一下这些问题 。

工作原理分析

1. 自动配置 JpaRepositoriesAutoConfiguration

因为我们已经将spring-data-jpa jar包,数据库驱动jar包都已经添加为了系统依赖,也就是说它们会出现在程序运行时的classpath上,并且,我们已经配置了数据源。现在在应用程序启动过程中,springboot的自动配置机制关于JPA自动配置的部分就开始工作了,具体的配置自动配置类是JpaRepositoriesAutoConfiguration

该自动配置有一点很重要,它导入了 JpaRepositoriesAutoConfigureRegistrar :

@Import(JpaRepositoriesAutoConfigureRegistrar.class)

JpaRepositoriesAutoConfigureRegistrar进而使用了注解@EnableJpaRepositories

@EnableJpaRepositories
private static class EnableJpaRepositoriesConfiguration {
}

注解@EnableJpaRepositories指出了创建repositoryFactoryBean是什么:

// 注解@EnableJpaRepositories部分源代码
	/**
	 * Returns the FactoryBean class to be used for each repository instance. Defaults to
	 * JpaRepositoryFactoryBean.
	 *
	 * @return
	 */
	Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;

这里指出缺省使用JpaRepositoryFactoryBean为创建用户自定义JpaRepositoryFactoryBean

JpaRepositoryFactoryBean位于包org.springframework.data.jpa.repository.support

2. 用户自定义JpaRepository作为bean注册到容器

因为JpaRepositoriesAutoConfiguration导入了JpaRepositoriesAutoConfigureRegistrar,而这是一个ImportBeanDefinitionRegistrar,设计目的就是用于Springboot自动配置机制中发现用户自定义JpaRrepository
在应用程序启动中ImportBeanDefinitionRegistrar被处理阶段,这个JpaRepositoriesAutoConfigureRegistrar会被执行:

// 调用栈
//SpringApplication#run
//SpringApplication#refreshContext
//AbstractApplicationContext#refresh
//AbstractApplicationContext#invokeBeanFactoryPostProcessors
//PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
//ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
//ConfigurationClassPostProcessor#processConfigBeanDefinitions
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
//ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
/**
* 参数registrars会包含一项是JpaRepositoriesAutoConfigureRegistrar
**/
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
	registrars.forEach((registrar, metadata) ->
			registrar.registerBeanDefinitions(metadata, this.registry));
}

JpaRepositoriesAutoConfigureRegistrar又将任务交给了一个RepositoryConfigurationDelegate来完成:

// 调用栈
// JpaRepositoriesAutoConfigureRegistrar#registerBeanDefinitions
// AbstractRepositoryConfigurationSourceSupport#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
		BeanDefinitionRegistry registry) {
	new RepositoryConfigurationDelegate(getConfigurationSource(registry),
			this.resourceLoader, this.environment).registerRepositoriesIn(registry,
					getRepositoryConfigurationExtension());
}
// 调用栈
// 接上
// AbstractRepositoryConfigurationSourceSupport#registerBeanDefinitions
//RepositoryConfigurationDelegate#registerRepositoriesIn
/**
 * Registers the found repositories in the given BeanDefinitionRegistry.
 *
 * @param registry
 * @param extension
 * @return BeanComponentDefinitions for all repository bean definitions found.
 */
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
		RepositoryConfigurationExtension extension) {

	if (LOG.isInfoEnabled()) {
		// 如果你的日志打开的话,控制台输出上应该能看到这个信息输出
		LOG.info("Bootstrapping Spring Data repositories in {} mode.", configurationSource.getBootstrapMode().name());
	}

	extension.registerBeansForRoot(registry, configurationSource);

	// 为接下来将要通过包扫描发现的用户自定义JpaRepository构造一个RepositoryBeanDefinitionBuilder,注意
	// 这个 builder 已经知道了对应将要到来的用户自定义JpaRepository bean注册时,都使用JpaRepositoryFactoryBean
	// 作为FactoryBean,这个信息是由@EnableJpaRepositories定义的,通过configurationSource参数传进来
	RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension,
			configurationSource, resourceLoader, environment);
	List<BeanComponentDefinition> definitions = new ArrayList<>();

	StopWatch watch = new StopWatch();

	if (LOG.isDebugEnabled()) {
		LOG.debug("Scanning for repositories in packages {}.",
				configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
	}

	watch.start();
	
	// extension.getRepositoryConfigurations() 会扫描相应的包并找到用户自定义JpaRepository接口
	Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
			.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode);

	Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap<>(configurations.size());

	for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : configurations) {

		configurationsByRepositoryName.put(configuration.getRepositoryInterface(), configuration);

		// 对于每个扫描找到的用户自定义JpaRepository,构建一个BeanDefinitionBuilder,
		// 就是在这个步骤中将该BeanDefinition同JpaRepositoryFactoryBean建立关系
		BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

		extension.postProcess(definitionBuilder, configurationSource);

		if (isXml) {
			extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
		} else {
			extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
		}

		// 这里根据所发现的用户自定义JpaRepository接口的名字构造一个bean名称
		AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
		String beanName = configurationSource.generateBeanName(beanDefinition);

		if (LOG.isTraceEnabled()) {
			LOG.trace(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName, configuration.getRepositoryInterface(),
					configuration.getRepositoryFactoryBeanClassName());
		}
		// 设置当前BeanDefinition的属性factoryBeanObjectType为用户自定义JpaRepository接口的全限定名
		beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());

		// 到目前为止针对所发现的一个用户自定义JpaRepository接口,已经构造了一个这样的bean定义:
		// 1. bean 名称根据用户自定义JpaRepository接口的短名称生成,比如:studentRepository
		// 2. bean 的类型为用户自定义JpaRepository接口
		// 3. bean 实际使用时的实现类将由JpaRepositoryFactoryBean工厂生成 (beanClass)
		// 现在把这个bean注册到容器
		registry.registerBeanDefinition(beanName, beanDefinition);
		definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
	}

	potentiallyLazifyRepositories(configurationsByRepositoryName, registry, configurationSource.getBootstrapMode());

	watch.stop();

	if (LOG.isInfoEnabled()) {
		// 如果你的日志打开的话,控制台输出上应该能看到对上面过程的一个总结
		LOG.info("Finished Spring Data repository scanning in {}ms. Found {} repository interfaces.", //
				watch.getLastTaskTimeMillis(), configurations.size());
	}

	return definitions;
}

从上面的分析来看,不管开发人员自定义了多少个JpaRepository,最终相应实际注入的bean都会来自由JpaRepositoryFactoryBean工厂来创建,而实际上,每个开发人员自定义的JpaRepository又是针对不同的领域实体的。这一区别又是怎么体现的呢?
我们继续分析。

3. 使用用户自定义JpaRepository bean

假如说服务层注入了一个用户自定义的JpaRepository,比如通过这种方式:

@Autowired
 StudentRepository repo;

那么在应用启动过程中,这个bean会被实例化并被注入。上面我们已经分析过,针对每个用户自定义的JpaRepository(在这个例子中,也就是StudentRepository repo)其实现类都来自JpaRepositoryFactoryBean工厂,现在我们看看这个bean的实例化过程。
首先每个用户自定义JpaRepository bean在首次被注入时,都会构造一个JpaRepositoryFactoryBean对象:

// 这里的参数就是用户自定义的JpaRepository接口	
	public JpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
		super(repositoryInterface);
	}

因为JpaRepositoryFactoryBean同时也实现了InitializingBean接口,所以在bean被创建后,它的方法afterPropertiesSet会被调用:

public void afterPropertiesSet() {
		// factory是用于创建用户自定义JpaRepository bean的工厂
		this.factory = createRepositoryFactory();
		
		// ......
		
		// 注意下面使用了 Lazy/Supplier 模式,代码看起来有些绕,
		// 实际上要做的事情是:
		// 1. 如果是 lazy 初始化模式,先不创建相应的 repository 实例,让它保存在一个 Lazy 对象中;
		// 2. 如果不是 lazy 初始化模式,直接创建相应的 repository 实例,也保存在 Lazy 对象中。
		// 这里 this.factory.getRepository 是真正创建 repository 实例的调用,
		// 因为这里的factory实际上是JpaRepository,我们下面会看它的getRepository方法实现		
		this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));

		if (!lazyInit) {
			// 如果不是lazy初始化模式,这里创建真正的的repository
			this.repository.get();
		}
	}

3.1 createRepositoryFactory

该方法的第一件事情,就是调用createRepositoryFactory,这个方法创建了一个JpaRepositoryFactory对象,用于创建最终开发人员自定义JpaRepository对应的bean实例。

protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
	// 这里创建了一个 JpaRepositoryFactory 对象
	JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
	jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);

	return jpaRepositoryFactory;
}

这里需要提醒的是,JpaRepositoryFactory类定义了生成最终JpaRepositorybean使用实现类SimpleJpaRepository:

@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
	// SimpleJpaRepository 位于包 org.springframework.data.jpa.repository.support
	return SimpleJpaRepository.class;
}

3.2 getRepository

JpaRepositoryFactory继承自RepositoryFactorySupport,其方法getRepository也在该基类中实现 :

public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {

		if (LOG.isDebugEnabled()) {
			LOG.debug("Initializing repository instance for {}…", repositoryInterface.getName());
		}

		Assert.notNull(repositoryInterface, "Repository interface must not be null!");
		Assert.notNull(fragments, "RepositoryFragments must not be null!");

		RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
		RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
		RepositoryInformation information = getRepositoryInformation(metadata, composition);

		validate(information, composition);

		// 下面语句最终创建一个 SimpleJpaRepository 对象,并且该对象是针对用户自定义JpaRepository 
		// repositoryInterface 的,注意参数 information 根据 repositoryInterface 生成,其中
		// 隐含了要操作哪个领域实体,或者说是操作哪个表。
		// 为什么会使用 SimpleJpaRepository ? 因为当前对象是一个JpaRepositoryFactory,
		// 它指定了要使用 SimpleJpaRepository 作为 repositoryBaseClass
		Object target = getTargetRepository(information);

		// Create proxy
		// SimpleJpaRepository 并不能满足用户自定义JpaRepository的所有方法,所以要为其创建
		// 一个代理对象,这样就可以增加更过的方法调用拦截器,对于那些自定义JpaRepository接口
		// 中定义但在SimpleJpaRepository的方法,就会由这些拦截器来处理。
		// 这里的代理同时也考虑了对异常,和事务的处理
		ProxyFactory result = new ProxyFactory();
		result.setTarget(target);
		result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);

		if (MethodInvocationValidator.supports(repositoryInterface)) {
			result.addAdvice(new MethodInvocationValidator());
		}

		result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
		result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

		postProcessors.forEach(processor -> processor.postProcess(result, information));

		result.addAdvice(new DefaultMethodInvokingMethodInterceptor());

		ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
		result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory));

		composition = composition.append(RepositoryFragment.implemented(target));
		result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));

		T repository = (T) result.getProxy(classLoader);

		if (LOG.isDebugEnabled()) {
		LOG.debug("Finished creation of repository instance for {}.", repositoryInterface.getName());
		}

		// 这里返回的是SimpleJpaRepository对象的代理对象
		return repository;
	}

从上面分析可见,方法getRepository创建了一个SimpleJpaRepository对象,该对象知道自己要针对哪个领域实体/数据库表进行操作,然后方法getRepository又创建了该SimpleJpaRepository对象的一个代理对象,附着上有关的Interceptor,返回该代理对象。

最后,当真正的JpaRepository对象(也就是上面分析中所提到的SimpleJpaRepository代理对象)被创建之后,包裹该对象的那个 JpaRepositoryFactoryBean对象就是最终我们要使用的bean的FactoryBean了,在Spring容器中,对应用户自定义的JpaRepository接口,保存bean的实际上是一个JpaRepositoryFactoryBean

现在,因为首次注入导致的bean的实例化过程已经结束了,可以进行真正的注入了。根据上面的分析,每个用户自定义JpaRepository接口在容器中的bean实际上保存的是一个JpaRepositoryFactoryBean对象,这是一个FactoryBean。对这样一个bean进行注入时,会调用FactoryBean#getObject获取真正要注入的SimpleJpaRepository代理对象。

总结

从上面的分析可以看到,虽然用户没有为每个自定义的JpaRepository接口提供实现类,但是Spring Data JPA 最终将每个这样的bean最终映射到了一个统一的实现类SimpleJpaRepository的代理对象,而这个代理对象能支持所有每个自定义的JpaRepository接口定义的功能和SimpleJpaRepository类中定义的所有功能。

问题 : 那些在用户自定义JpaRepository中定义但是并不属于SimpleJpaRepository的方法,是怎样转换成相应的SQL的?