作者:张葛 职位:后端工程师
从我们进⼊编程的世界,成为程序员到现在为⽌,总有⼏个感觉神奇和激动的时刻,其中肯定包括你第⼀次程序连上数据库可以实现 CURD 功能的时候,就算那时的我们写着千遍⼀律 JDBC 模板代码也是乐此不疲。
时代在不断进步,技术也不断在发展,市面上已经有很多优秀的数据库持久化框架供我们使用,今天我将带大家来了解JPA的使用。
在我们现在Spring Boot横行无忌的时代,在项目中引入JPA非常简单,我们以Maven以及常用的MySQL数据库为例
在pom.xml文件添加以下依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/>
</parent>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在Spring Boot的YML文件中添加以下内容
spring:
datasource:
driver-class-name:
com.mysql.cj.jdbc.Driver
url:jdbc:mysql://localhost:3306/sakila?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8 username: root
password: root
定义Entity类,我测试过程使用的数据源为MySQL官方提供的样例数据库sakila,大家可以在https://dev.mysql.com/doc/index-other.html自行下载
@Entity
@Data // lombok注解
public class Actor {
@Id
// Column注解不是必须的,如果满足字段驼峰形式
// 与数据库字段以下划线分隔形式对应即可
@Column(name = "actor_id", nullable = false)
private Integer actorId;
@Column(name = "first_name", nullable = false, length = 45)
private String firstName;
private String lastName;
private Timestamp lastUpdate;
}
定义Respository接口,一般我们通过继承JpaRepository接口即能满足我们一般的CURD操作,如果需要支持复杂逻辑查询
比如:动态SQL;联表查询,则需要继承 JpaSpecificationExecutor 接口,并配合Specification的接口方法。
@Repository
public interface ActorRepository extends JpaRepository<Actor, Integer> {
}
在JpaRepository的中我们可以看到有findAll、getById、findById、save、saveAll、deleteById...这些默认定义,这是JPA为我们提供的常用的一些数据操作,我们可以直接使用,极大提高了日常的开发效率。
正是因为这一便利的让人着迷的特性,让我们乐于去使用JPA这一持久化框架,接下来让我们通过几个例子去欣赏它的迷人之处吧。
/**
* 示例1
* SQL: SELECT * FROM actor WHERE first_name = ?
* 参数名的定义不影响程序的运行
*/
List<Actor> findByFirstName(String name);
/**
* 示例2
* SQL: SELECT * FROM actor WHERE first_name = ? AND last_name = ?
* 如果明确知道查询结果返回唯一一条记录时,建议使用单一实体类作为返回类型
*/
List<Actor> findByFirstNameAndLastName(String name1, String name2);
/**
* 示例3
* SQL: SELECT * FROM actor WHERE actor_id <= ?
*/
List<Actor>findByActorIdLessThanEqual(Integer id);
遵照JPA的规范,通过定义类似以上接口方法的形式就可以零SQL实现我们需要的单表查询(不能实现DML操作)操作。JPA对此类查询方式有很丰富的支持,受限于篇幅,我们就不一一讲述了,详细的内容可以阅读官方文档
地址:
https://docs.spring.io/spring-data/jpa/docs/2.5.6/reference/html/#repository-query-keywords
Tips
1.在查询场景中,自定义的查询接口中,find关键词(也可以是search、query、get)后面必须跟随By关键词
2.Between适用于数值、日期字段,用于日期时,参数类型可以是java.util.Date或java.sql.Timestamp
List<Film> findByLengthBetween(Integer low, Integer up);
List<Film> findByLastUpdateBetween(Date startDate, Date endDate);
3.IsEmpty / IsNotEmpty只能用于集合类型的字段
4.Before或者After可用于日期、数值类型的字段
List<Film> findByLengthBefore(Integer length)
5.涉及到删除和修改时,需要在方法定义上加上@Modifying
JPQL是通过Hibernate的HQL演变过来的,它和HQL语法及其相似。
/**
* 示例1
* SQL: SELECT * FROM actor WHERE first_name = ?
*/
@Query("FROM Actor WHERE firstName = ?1")
List<Actor> findByFirstName(String name);
/**
* 示例2
* SQL: SELECT * FROM actor WHERE first_name = ? AND last_name = ?
*/
@Query("FROM Actor WHERE firstName = ?1 AND lastName = ?2")
List<Actor> findByFirstNameAndLastName(String name1, String name2);
/**
* 示例3
* SQL: SELECT * FROM actor WHERE actor_id <= ?
*/
@Query("FROM Actor WHERE actorId <= ?1")
List<Actor> findByActorIdLessThanEqual(Integer id);
/**
* 示例4
* SQL: SELECT * FROM actor
* 不能写"SELECT *" 要写"SELECT 别名"
*/
@Query("SELECT a FROM Actor a")
List<Actor> findAll()
通过以上例子我们发现,JPQL与SQL的区别是,SQL是面向对象关系数据库,它操作的是数据表和数据列,而JPQL操作的对象是实体对象和实体属性,区分大小写,出现的SQL关键字还是原有的意思,不区分大小写。JPQL也可以支持复杂的联表查询。下面是JPQL的基本格式:
👉SELECT 实体别名.属性名, 实体别名.属性名 FROM 实体名 AS 实体别名 WHERE 实体别名.实体属性 op 比较值看完查询的写法,我们来看看DML操作的写法
@Modifying
@Query(value = "UPDATE Film SET description = :description WHERE filmId = :id")
void update(@Param("id") Integer filmId, @Param("description") String description);
在Query注解中,有一个属性字段nativeQuery,默认情况下为false,即为JPQL模式,如果我们设置为true,则我们可以value属性中定义原生SQL语句实现数据库操作。
@Query("SELECT * FROM actor WHERE first_name = ?1", nativeQuery = true)
List<Actor> findByFirstName(String name);
@Query("SELECT * FROM actor WHERE first_name = ?1 AND last_name = ?2", nativeQuery = true)
List<Actor> findByFirstNameAndLastName(String name1, String name2);
@Query("SELECT * FROM actor WHERE actor_id <= ?", nativeQuery = true)
List<Actor> findByActorIdLessThanEqual(Integer id);
@Query("SELECT first_name, last_name FROM actor", nativeQuery = true)
List<Map<String, Object>> findAll();
// List<String[]> findAll();
通过上述示例,我们总结几点编写原生SQL需要注意的地方
1.如果接口方法返回的是实体对象,如List<Actor>、Actor这样的,则SELECT部分不能指定字段名,必须为*
2.如果只想查询指定列的数据,方法定义时的返回类型可以是Map<String, Object>、String[],返回数据如果是多条,则用集合类嵌套
3.关于JPA接口方法的返回类型我们可以参考官方文档,地址为:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-return-types
通过前面的学习,我们已经对JPA有了比较清晰的了解,不再是门外汉了,当然我们还是会有很多的疑问:
○为什么我们只是加入了一个Maven的依赖,就能直接使用JPA?
○为什么我们只是定义了一个Repository的接口就能直接使用它来实现数据库操作逻辑?
○为什么我们只是按照JPA的规范,定义了一个接口方法就能在使用时生成想要的SQL?
在接下来的学习中一一解开。
虽然这一节是要讲JPA的自动配置,其实本质要说的是Spring Boot自动加载配置的原理。在我们使用Spring Boot创建一个项目时,必不可少的一个注解就是@SpringBootApplication,看着它我们既熟悉又陌生。我们通过它的源码来一探究竟。
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
通过源码我们发现其实SpringBootApplication也被一些注解所修饰,其中就有EnableAutoConfiguration注解,一看名字我们就知道它就是我们今天要找的正主。同样的,我们也不会放过它的源码的。
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我们又看到了熟悉的身影,@Import注解,它意味着在我们的IOC容器中引入一个AutoConfigurationImportSelector对象。
通过源码我们又发现AutoConfigurationImportSelector最终实现了ImportSelector接口。因此在程序的启动过程中,会执行selectImports方法,在当前的实现中,这个方法的主要逻辑是去读取一个 spring.factories下key为EnableAutoConfiguration对应的全限定名的值。
spring.factories里面配置的那些类,主要作用是告诉 Spring Boot这个stareter所需要加载哪些xxxAutoConfiguration类,也就是你真正的要自动注册的那些bean或功能。
而在SpringBoot中的META-INF/spring.factories(完整路径:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中关于EnableAutoConfiguration的这段配置如下 :
可以发现有JpaRepositoriesAutoConfiguration和HibernateJpaAutoConfiguration帮我们配置了JPA 相关的配置。至此,我们的第一个疑惑可以解开了。
接下来我们要来解决第二个疑惑,其实本节的标题已经告诉了我们答案。先上源码:
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
}
我们不用看具体实现的方法逻辑了,单从SimpleJpaRepository的声明来看,它是一个类,不是抽象类是一个实现了JpaRepositoryImplementation接口的类
实际上JpaRepositoryImplementation接口也继承了JpaRepository和JpaSpecificationExecutor接口。
到此,我们可以大胆的猜测,为什么我们定义的repository接口只是继承了JpaRepository和JpaSpecificationExecutor接口就能使用一系列的接口调用,其实是SimpleJpaRepository类帮我们做了要做的事情。
那到底是不是,如果是的,那JPA是怎么做到了的呢?我们继续一步一步分析。首先我们得有一个分析的起点,上一节我们讲了JPA的自动配置,那我们是不是需要看看它到底配置了什么,看看能不能找到我们想要的。我们直接看JpaRepositoriesAutoConfiguration,因为它的名字里带Repositories。
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true",
matchIfMissing = true)
@Import(JpaRepositoriesRegistrar.class)
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })
public class JpaRepositoriesAutoConfiguration {
}
我们好像运气不错,果然发现有一个Import注解引入了一个JpaRepositoriesRegistrar类,从名字看,JPA repository注册器,好像越来越接近了,直接上源码:
class JpaRepositoriesRegistrar extends AbstractRepositoryConfigurationSourceSupport {
}
public abstract class AbstractRepositoryConfigurationSourceSupport
implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
private BeanFactory beanFactory;
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(
getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment);
delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension());
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registerBeanDefinitions(importingClassMetadata, registry, null);
}
// 其他源码
...
}
其实分析了这么久,如果我们经常去看看Spring系的一些源码,会发现很多老朋友。我们看到其中一个关键接口ImportBeanDefinitionRegistrar,它的关键方法就是registerBeanDefinitions了,就是按照我们的实现逻辑把一些我们需要的bean注册到IOC容器中。分析到这,我们通过测试代码调试来看看吧。
我们可以看到debug窗口中,在测试类中注入的CustomerRepository接口的实际对象是JdkDynamicAopProxy类型,这是一个jdk动态代理类,这确实比较符合我们对Spring IOC容器对接口类注入处理的认识。我们看一下它代理的目标类,发现是SimpleJpaRepository类,如此就证实了我们之前的猜测。第一次调试到此结束。
我们再回过头来看我们之前找到的AbstractRepositoryConfigurationSourceSupport类中registerBeanDefinitions方法,这个是在项目启动过程中执行的,我们加上断点进行第二次调试。
registerBeanDefinitions方法中主要的逻辑在delegate.registerRepositoriesIn中实现,我们直接在里面标记上断点。
当执行到上面断点后,我们看到configurations对象中已经有了我们自己定义的repository接口的信息了,接下来我们将进入for循环分别处理我们定义的接口了,我们进到下一个断点继续看。
在当前断点,我们可以看到,beanName是符合我们正常创建IOC容器中bean的命名规则的,但是JPA为我们创建的BeanDefinition对象是org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean类型,它是一个FactoryBean。我们继续看187、189行的代码
JPA将我们的BeanDefinition对象设置了一个name为factoryBeanObjectType,value为当前被处理的repository接口的全限定类名(比如ai.advance.jpademo.repository.FilmRepository)的attribute
最后将BeanDefinition对象注册到了我们的IOC容器中。因此当for循环执行完后,如果我们使用repository接口对应的beanName从IOC容器中获取一个实例,最开始我们拿到的是一个JpaRepositoryFactoryBean类型的Bean,当然我们最后真正拿到的bean对象是由getObject方法返回的对象。我们先来看一下它的继承关系:
JpaRepositoryFactoryBean既是一个工厂,又是一个bean。其作用类似于@Bean注解,但比其能实现更多复杂的功能,可以对对象增强。重要方法是getObject(),返回一个bean,因此我们去看一下getObject方法的实现,实际它的getObject方法的实现在其父类RepositoryFactoryBeanSupport中。
@Nonnull
public T getObject() {
return this.repository.get();
}
好像没什么东西,有点懵,那我们先忽略它,我们再一想,JpaRepositoryFactoryBean对象也是一个Bean,那有没有一点和bean实例化过程相关的代码,然后我们找到了这块代码:
@Override
public void afterPropertiesSet() {
Assert.state(entityManager != null, "EntityManager must not be null!");
super.afterPropertiesSet();
}
而确实JpaRepositoryFactoryBean的父类RepositoryFactoryBeanSupport也实现了InitializingBean接口,它自身也是调用了父类的afterPropertiesSet方法。
// 以下代码实现在org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport中
public void afterPropertiesSet() {
this.factory = createRepositoryFactory();
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
this.factory.setNamedQueries(namedQueries);
this.factory.setEvaluationContextProvider(
evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT));
this.factory.setBeanClassLoader(classLoader);
this.factory.setBeanFactory(beanFactory);
if (publisher != null) {
this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher));
}
repositoryBaseClass.ifPresent(this.factory::setRepositoryBaseClass);
this.repositoryFactoryCustomizers.forEach(customizer -> customizer.customize(this.factory));
RepositoryFragments customImplementationFragment = customImplementation //
.map(RepositoryFragments::just) //
.orElseGet(RepositoryFragments::empty);
RepositoryFragments repositoryFragmentsToUse = this.repositoryFragments //
.orElseGet(RepositoryFragments::empty) //
.append(customImplementationFragment);
this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);
this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));
// Make sure the aggregate root type is present in the MappingContext (e.g. for auditing)
this.mappingContext.ifPresent(it -> it.getPersistentEntity(repositoryMetadata.getDomainType()));
if (!lazyInit) {
this.repository.get();
}
}
逻辑有点多,我们先看到这一行代码:
this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));
先提一句Lazy类继承了java.util.function.Supplier接口。这个this.repository我们是不是在getObject方法有看到,我们还发现它的get方法返回的结果是由this.factory.getRepository()得到的,那这个this.factory又是谁呢,这时我们就要返回来看afterPropertiesSet方法的第一行代码:
直接看createRepositoryFactory方法,它是在JpaRepositoryFactoryBean的直接父类TransactionalRepositoryFactoryBeanSupport中实现的。
protected final
RepositoryFactorySupport createRepositoryFactory() {
RepositoryFactorySupport factory = doCreateRepositoryFactory();
RepositoryProxyPostProcessor exceptionPostProcessor = this.exceptionPostProcessor;
if (exceptionPostProcessor != null) {
factory.addRepositoryProxyPostProcessor(exceptionPostProcessor);
}
RepositoryProxyPostProcessor txPostProcessor = this.txPostProcessor;
if (txPostProcessor != null) {
factory.addRepositoryProxyPostProcessor(txPostProcessor);
}
return factory;
}
我们直接看关键代码 -- doCreateRepositoryFactory方法,这个是由JpaRepositoryFactoryBean类自己实现的。
protected RepositoryFactorySupport doCreateRepositoryFactory() {
Assert.state(entityManager != null, "EntityManager must not be null!");
return createRepositoryFactory(entityManager);
}
/**
* Returns a {@link RepositoryFactorySupport}.
*/
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter);
if (queryMethodFactory != null) {
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
}
return jpaRepositoryFactory;
}
我们直接看到createRepositoryFactory方法,它最后返回是一个JpaRepositoryFactory类型的对象,其他的我们就先不关心了,直接看JpaRepositoryFactory类。我们先来一张类关系图:
我们直奔我们的主题-- getRepository方法,它是在JpaRepositoryFactory的父类RepositoryFactorySupport中实现的。
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
...
RepositoryInformation information = getRepositoryInformation(metadata, composition);
...
Object target = getTargetRepository(information);
...
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
...
T repository = (T) result.getProxy(classLoader);
...
return repository;
}
实现代码有点多,这里仅展示我们关注的关键代码,去掉其他多余的代码之后,有没有发现上面的逻辑很熟悉,其实就是构建一个代理类实例的过程,因此也解释了为什么我们在第一次调试看到实际注入的是一个JdkDynamicAopProxy类型的实体。那我们得好好看看代理对象的目标对象是怎么得到的,请看getTargetRepository方法,发现他需要一个RepositoryInformation类型的传参,我继续往上找,看到了getRepositoryInformation方法。
private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,
RepositoryComposition composition) {
RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition);
return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {
Class<?> baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata));
return new DefaultRepositoryInformation(metadata, baseClass, composition);
});
}
其中,可以看到一个getRepositoryBaseClass方法
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleJpaRepository.class;
}
好了,什么也不用说了,它直接给我们返回了SimpleJpaRepository的Class对象。其他的逻辑我们也不看了,虽然还有一些包装和判断的过程,但是我们今天的目的已经达到了,第二个疑惑也算比较好的解答了。最后顺便提一句,如果你的项目是手动使用了@EnableJpaRepositories注解,可能你的调试过程开局会有点不一样,但是后续的逻辑是相同的,自己可以去试试。代码如下:
@EnableJpaRepositories(basePackages = "ai.advance.jpademo.repository")
@SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
}
我们还有一个疑问,为什么我们自己定义的那些不属于SimpleJpaRepository类的方法也能被调用,并且被正确生成SQL?我们又要回到上节分析的getRepository方法,同样的我们去掉不需要关心的代码。
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
...
ProxyFactory result = new ProxyFactory();
...
Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
evaluationContextProvider);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
namedQueries, queryPostProcessors, methodInvocationListeners));
result.addAdvice(
new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners));
T repository = (T) result.getProxy(classLoader);
...
return repository;
}
从上面的源码我们可以看到ProxyFactory对象在最后有两次addAdvice方法的调用,目的是为了增加QueryExecutorMethodInterceptor
和ImplementationMethodExecutionInterceptor两个拦截器,它们都实现了MethodInterceptor接口,其中ImplementationMethodExecutionInterceptor为RepositoryFactorySupport的静态内部类。
QueryExecutorMethodInterceptor这个拦截器是用来拦截处理我们在repository接口中自定义的方法的。在QueryExecutorMethodInterceptor的成员变量中有一个定义为Map<Method, RepositoryQuery>类型的queries变量,这个变量主要保存了自定义方法对象与一个RepositoryQuery对象的映射关系。
RepositoryQuery的直接抽象子类是AbstractJpaQuery,可以看到,一个RepositoryQuery实例持有一个JpaQueryMethod实例,JpaQueryMethod又持有一个Method实例,所以RepositoryQuery实例的用途很明显,一个RepositoryQuery代表了Repository接口中的一个方法,根据方法头上注解不同的形态,将每个Repository接口中的方法分别映射成相对应的RepositoryQuery实例。我们通过类关系图来熟悉一下RepositoryQuery具体的实现类有哪些。
下面我们看看JPA在哪些情况下创建对应的那个RepositoryQuery对象
1.SimpleJpaQuery
方法头上@Query注解的nativeQuery属性缺省值为false,也就是使用JPQL,此时会创建SimpleJpaQuery实例,并通过两个StringQuery类实例分别持有query JPQL语句和根据query JPQL计算拼接出来的countQuery JPQL语句2.NativeJpaQuery
方法头上@Query注解的nativeQuery属性如果显式的设置为nativeQuery=true,也就是使用原生SQL的时候3.PartTreeJpaQuery
方法头上未进行@Query注解,将使用spring-data-jpa独创的方法名识别的方式进行sql语句拼接4.NamedQuery
使用javax.persistence.NamedQuery注解访问数据库的形式的时候5.StoredProcedureJpaQuery
在Repository接口的方法头上使用org.springframework.data.jpa.repository.query.Procedure注解,也就是调用存储过程的方式访问数据库的时候所以QueryExecutorMethodInterceptor最终的目的就是根据当前需要调用的自定义的Repository的方法找到对应的RepositoryQuery对象,并构建调用信息并使用invoke方法触发调用,主要逻辑在QueryExecutorMethodInterceptor类的doInvoke方法中。
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (hasQueryFor(method)) {
RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);
if (invocationMetadata == null) {
invocationMetadata = RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method));
invocationMetadataCache.put(method,
invocationMetadata);
}
return invocationMetadata.invoke(repositoryInformation.getRepositoryInterface(), invocationMulticaster,
invocation.getArguments());
}
return invocation.proceed();
}
其实最终调用的是RepositoryMethodInvoker类中的doInvoke方法,我们打上断点来看一下
ImplementationMethodExecutionInterceptor这个拦截器是来处理SimpleJpaRepository类本身实现的方法调用的。它是RepositoryFactorySupport类的静态内部类,只要没有被QueryExecutorMethodInterceptor拦截器处理的方法调用都会由它来处理,最终也是调用invoke方法。
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
try {
return composition.invoke(invocationMulticaster, method, arguments);
} catch (Exception e) {
org.springframework.data.repository.util.ClassUtils.unwrapReflectionException(e);
}
thro
w new IllegalStateException("Should not occur!");
}
从上面信息可以看出来ImplementationMethodExecutionInterceptor类的invoke方法调用的是RepositoryComposition类的invoke方法,如果继续深入,其实最终也是调用的RepositoryMethodInvoker类中的doInvoke方法
至此,我们开头的几大疑惑基本都得到了解释。
四、总结
我也是尽量把⾃⼰知道的知识写明⽩,奈何本⼈⽔平有限,接触JPA也不久,如果存在纰漏欢迎指正。关于JPA更深层次的东⻄我也还在学习中,希望之后有机会再分享。