Spring Data JPA從入門到精通(第一部分)
Spring Data JPA從入門到精通(第二部分)
Spring Data JPA從入門到精通(第三部分)
所有代碼均源自spring-data#2.2.6版本
這一部分由于篇幅較少,看完還有很多疑問,就按照自己的思路重新整理了下.
一.整體認(rèn)識JPA
推薦:
JPA的根本目標(biāo)是實(shí)現(xiàn)將面向?qū)ο蟮拇鎯?chǔ)與底層所提供的持久化機(jī)構(gòu)解耦椒惨;也就是說,從面向?qū)ο蟪绦蜷_發(fā)者的角度來說,我不需要知道你底層的數(shù)據(jù)庫是什么,Oracle也好、MySQL也好谭企、DB2也好淹办,我不關(guān)心, 我只需要你(JPA)幫我將我需要的對象數(shù)據(jù)(Object)持久化(寫入存儲(chǔ))或反序列化(讀出存儲(chǔ))即可。
JPA 只關(guān)注與關(guān)系型數(shù)據(jù)庫, 非關(guān)系型(如Redis,MongoDB)則由Spring Data 來定義.
Spring的視野下幾個(gè)Lib的關(guān)系:
可以看出Spring-Data-Jpa作為Spring-Data的子項(xiàng)目,把工作職責(zé)限定在關(guān)系型DB,它依賴于Spring-Data-Commons和Spring-orm,而orm又依賴于Hibernate實(shí)現(xiàn).換句話說,也就是 Commons 比較超然,并不會(huì)和ORM模式直接關(guān)聯(lián).
二. JPA的 Spring Boot自動(dòng)裝配
-
自動(dòng)裝備
Spring-Boot-JPA支持自動(dòng)裝配,只需要在dependency中加上spring-boot-starter-data-jpa,而無需任何配置,即可自動(dòng)識別@Entity和@Repository(甚至這個(gè)都可以沒有),把倉庫組裝好.
SpringBoot 中的 META-INF/spring.factories(完整路徑:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中關(guān)于 EnableAutoConfiguration 的這段配置如下 :
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
可以發(fā)現(xiàn) JpaRepositoriesAutoConfiguration 和 HibernateJpaAutoConfiguration 幫我們裝置了 JPA 相關(guān)的配置焚志。
- 在@SpringBootApplication中就包含@EnableAutoConfiguration,而它就包含@Import(AutoConfigurationImportSelector.class)
- 在AbstractApplicationContext#invokeBeanFactoryPostProcessors()方法中會(huì)調(diào)用AutoConfigurationImportSelector#process(),它會(huì)遍歷所有jar中的META-INF/spring.factories.
- 在getCandidateConfigurations()方法中,就會(huì)找到spring-boot-autoconfigure-××.jar中配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration一節(jié)內(nèi)容,變成List<String> configurations.這個(gè)list包含了我們關(guān)心的JdbcRepositoriesAutoConfiguration和JpaRepositoriesAutoConfiguration
- 看看代碼
// 默認(rèn)的proxyBeanMethods模式是false
@Configuration(proxyBeanMethods模式是false = false)
// 需要配置JDBC先
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })
// 留個(gè)口子,即時(shí)加了Spring-data-jpa的依賴,也可以通過配置關(guān)閉,默認(rèn)是開啟的.
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true",
matchIfMissing = true)
// 這個(gè)是核心注解
@Import(JpaRepositoriesRegistrar.class)
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })
public class JpaRepositoriesAutoConfiguration {
@Bean
@Conditional(BootstrapExecutorCondition.class)
public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer(
Map<String, AsyncTaskExecutor> taskExecutors) {
...
}
}
我們發(fā)現(xiàn),這里又Import了另外一個(gè)配置JpaRepositoriesRegistrar
- JpaRepositoriesRegistrar從名字我們就可以看出,他是一個(gè)配置注冊器,其擴(kuò)展了AbstractRepositoryConfigurationSourceSupport,在這個(gè)類可以看到熟悉的registerBeanDefinitions方法.
而且在JpaRepositoriesRegistrar還有個(gè)內(nèi)部靜態(tài)Class,它@EnableJpaRepositories,spring-boot把這個(gè)藏在這里了,因此我們不需要自己去Enable
-
解析用戶倉庫接口
既然找到了注冊器,那么接下來就來看看如果解析并生成倉庫類,也就是JPA最牛的地方,我們只需要定義接口,其他由JPA替我們實(shí)現(xiàn).
- AbstractRepositoryConfigurationSourceSupport#registerBeanDefinitions()方法就是入口了.
- 跟蹤代碼進(jìn)入RepositoryConfigurationDelegate#registerRepositoriesIn(),其中
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode);
這句會(huì)找到我們寫的接口(extends JpaRepository<>)
圖:
- 找到了列表,就開始逐個(gè)遍歷,這里通過BeanDefinitionBuilder構(gòu)造出org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean,并注冊到registry,也就是DefaultListableBeanFactory中去.這樣我們的BeanFactory也知道這個(gè)倉庫接口了.
-
創(chuàng)建接口實(shí)現(xiàn)
- refresh()方法的finishBeanFactoryInitialization()中,創(chuàng)建JpaRepositoryFactoryBean的實(shí)例.
- AbstractBeanFactory#doGetBean()創(chuàng)建beanName為"userRepository"的實(shí)例
// Create bean instance.
if (mbd.isSingleton()) {
// 我們的Repository都是單例的
sharedInstance = getSingleton(beanName, () -> {
try {
// 這里構(gòu)造Bean
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
這里就會(huì)創(chuàng)建出這個(gè)工廠factory,在工廠bean創(chuàng)建后的afterPropertiesSet,會(huì)調(diào)用
this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));
此時(shí),使用工廠方法根據(jù)我們定義的接口,創(chuàng)建出Repository代理類.之后再調(diào)用get()方法創(chuàng)建出實(shí)例.
注意:創(chuàng)建代理類使用的是RepositoryFactorySupport#getRepository()方法,其中QueryExecutorMethodInterceptor負(fù)責(zé)拆開我們自定義的方法如(findByName),如果名字有錯(cuò)誤,那么在
new QueryExecutorMethodInterceptor(information, projectionFactory)
this.queries = lookupStrategy //
.map(it -> mapMethodsToQuery(repositoryInformation, it, projectionFactory)) //
.orElse(Collections.emptyMap());
時(shí)就會(huì)出錯(cuò),這里用了好多l(xiāng)ambda表達(dá)式.繼續(xù)跟蹤,發(fā)現(xiàn)是JpaQueryMethod負(fù)責(zé),構(gòu)造JpaQueryMethod后,調(diào)用JpaQueryLookupStrategy#resolveQuery()解析.
@Override
protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) {
RepositoryQuery query = JpaQueryFactory.INSTANCE.fromQueryAnnotation(method, em, evaluationContextProvider);
if (null != query) {
return query;
}
query = JpaQueryFactory.INSTANCE.fromProcedureAnnotation(method, em);
if (null != query) {
return query;
}
// 這個(gè)name就是解析出的參數(shù)名了,例如findByName,就會(huì)解析出name
String name = method.getNamedQueryName();
if (namedQueries.hasQuery(name)) {
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
evaluationContextProvider);
}
query = NamedQuery.lookupFrom(method, em);
if (null != query) {
return query;
}
// 發(fā)現(xiàn)name找不到具體對應(yīng)的屬性,這里拋出異常
throw new IllegalStateException(
String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method));
}
也就是說,它是在啟動(dòng)時(shí)將接口方法解析為HQL,如果接口方法參數(shù)有問題,在"啟動(dòng)"階段而非"編譯"階段會(huì)發(fā)現(xiàn)錯(cuò)誤.
最重要的圖3-3
推薦閱讀: CSDN---Spring-data-jpa 圖文課
spring data jpa 全面解析(實(shí)踐 + 源碼分析)
三. JPA規(guī)范與接口
JPA接口規(guī)范
從javax.persistence-api-2.2規(guī)范里面可以看出,JPA比起JDBC規(guī)范真是復(fù)雜多了;暫時(shí)沒有實(shí)力去逐個(gè)了解.抓住幾個(gè)重點(diǎn)先,雖然可能并不完全正確,但是有利于理解后面看JPA實(shí)現(xiàn)的源碼:
-
criteria包定義了JPA對于SQL的抽象(如CriteriaQuery代表查詢,CriteriaUpdate代表更新)
- Expression是表達(dá)式,具體來說,可以代表SQL中Id=1這一小段.
- Predicate是一組Boolean型的Expression,代表Where后面一整串.
- Root和Path,Root代表整個(gè)查詢的Entity,也就是表對象,而Path是其中一部分,也就是某一列.
- CriteriaBuilder是一個(gè)構(gòu)建器,可以方便地構(gòu)造出CriteriaQuery,CriteriaUpdate并且?guī)е髯詷?gòu)建Expression和組合Expresssion的方法.
-
metamodel包定義了JPA對于Object的抽象,類似于Spring的BeanDefinition
- ManagedType擴(kuò)展了Type,代表了一個(gè)受控的元模型
- Metamodel是JPA的元模型類,可以根據(jù)Class類型獲得EntityType或managedType
-
spi包定義要實(shí)現(xiàn)SPA需要實(shí)現(xiàn)的一些接口
- PersistenceProvider規(guī)定了createEntityManagerFactory,generateSchema并能獲得當(dāng)前的ProviderUtil實(shí)例.
頂層包倒是我們比較熟悉的一些注解,如 @Entity, @Id, @OneToMany, @SequenceGenerator等等.
可以看出,JPA的抽象簡直是智力游戲級別的,就是朝著讓人看不懂方向去的,不是Hibernate的專家,應(yīng)該搞不懂為啥要這么去做吧.因此只能大概過一下,有個(gè)概念.
JpaRepository繼承樹與實(shí)現(xiàn)
圖: 繼承樹
從繼承樹可以看出,QuerydslJpaRepository已被廢棄,而我們需要查看的實(shí)現(xiàn)類是SimpleJpaRepository,那么具體的查詢是如何執(zhí)行的呢,可以分以下兩種情況:
如果是JPA的標(biāo)準(zhǔn)實(shí)現(xiàn)如findById()這種,就直接調(diào)用使用代理攔截,由SimpleJpaRepository代替接口完成查詢工作.
如果是自定義的方法,就需要結(jié)合之前解析的內(nèi)容,在resolveQuery后進(jìn),把查詢條件都保存在
QueryExecutorMethodInterceptor的queries中,它是一個(gè)private final Map<Method, RepositoryQuery>,這樣我們自定義的方法,就可以根據(jù)名字,對應(yīng)到一個(gè)RepositoryQuery上面了.
之前在構(gòu)造Bean時(shí)候,已經(jīng)解析好了.因而在程序中調(diào)用的時(shí)候,自然就進(jìn)入了JdkDynamicAopProxy#invoke()方法,然后它會(huì)調(diào)用RepositoryFactorySupport#doInvoke()方法.
@Nullable
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (hasQueryFor(method)) {
return queries.get(method).execute(invocation.getArguments());
}
return invocation.proceed();
}
就可以找到對應(yīng)的PartTreeJpaQuery#execute()進(jìn)行查詢了,這種就沒有過SimpleJpaRepository.
四.Spring-Data-JPA查詢方式的使用
-
@Query(JPQL標(biāo)準(zhǔn))
略
-
方法名稱查詢
上面的QueryExecutorMethodInterceptor完成
-
Example查詢(6.2-QueryByExpampleExecutor)
- 通過傳入一個(gè)Example樣本S,去查找類似的數(shù)據(jù).當(dāng)然最終也是通過Specification實(shí)現(xiàn).
- ExampleMatcher通過相當(dāng)簡單的規(guī)則,對樣本數(shù)據(jù)進(jìn)行篩選條件的限制;包括nullHandler(空值處理),StringMatcher(字符串匹配方式),IgnoreCase(大小寫忽略方式),properSepcifiers(屬性特定查詢方式),ignoredPaths(忽略屬性列表).
-
Specification查詢查詢(6.3-JpaSpecificationExecutor)
- Specification核心方法
/**
* Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
* {@link Root} and {@link CriteriaQuery}.
*
* @param root must not be {@literal null}.
* @param query must not be {@literal null}.
* @param criteriaBuilder must not be {@literal null}.
* @return a {@link Predicate}, may be {@literal null}.
*/
@Nullable
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
至于怎么用,書里面說的并不詳細(xì),建議看看官方文檔了.
五.Spring-Data-JPA事務(wù)的實(shí)現(xiàn)
JPA實(shí)際也是通過JpaRepositoryFactory實(shí)現(xiàn)的.
六.擴(kuò)展能力
- Audit擴(kuò)展
Spring Data JPA為我們提供了審計(jì)功能的架構(gòu)實(shí)現(xiàn),提供了4個(gè)專門的注解:@CreateBy,@CreateDayte,@LastModifiedBy,@LastModifiedDate
具體使用步驟:
一. @Entity增加AutityListener,并增加上述4個(gè)注解
二. 實(shí)現(xiàn)AuditorAware接口,告訴JPA當(dāng)前用戶(推薦統(tǒng)一從Request中取)
三. 通過@EnableJpaAuditing注解開啟JPA的審計(jì)功能
這樣,每次在修改表的同時(shí),也自動(dòng)添加了審計(jì)的信息
- Listener
從審計(jì)的實(shí)現(xiàn)可以看出,他是通過定義事件處理完成的.
JPA提供了CallBack鉤子(這個(gè)好像GORM的實(shí)現(xiàn)),在數(shù)據(jù)庫操作過程可以自定義EntityListener,并且回填entity對象,這樣就能很方便擴(kuò)展.
@Prepersist注解的方法 映凳,完成save之前的操作胆筒。
@Preupdate注解的方法 娘纷,完成update之前的操作锋爪。
@PreRemove注解的方法 ,完成remove之前的操作膳沽。
@Postpersist注解的方法 矫渔,完成save之后的操作彤蔽。
@Postupdate注解的方法 ,完成update之后的操作庙洼。
@PostRemovet注解的方法 顿痪,完成remove之后的操作。
- Version
JPA通過@Version幫我們處理樂觀鎖,只需要增加一個(gè)long類型的version字段,每次save的時(shí)候,JPA都會(huì)幫我們自增該字段.(這樣當(dāng)出現(xiàn)多線程同時(shí)save,就有可能出現(xiàn)樂觀鎖更新失敗的情況)
需要我們自行捕獲ObjectOptimisticLockingFailureException處理.
通過 JpaMetamodelEntityInformation#IsNew()方法
@Override
public boolean isNew(T entity) {
if (!versionAttribute.isPresent()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
這里吧@Version的那個(gè)屬性變成了versionAttribute,這里會(huì)判斷一下.
- 分頁排序
這個(gè)是同Spring Web Mvc配合使用的.
- 通過@EnableSpringDataWebSupport可以自動(dòng)注入Repository對象.---還是顯式注入的好.
- 通過handlerMethodArgumentResolvers可以實(shí)現(xiàn)分頁和排序.
參考:
https://gitee.com/staticsnull/Y2T6014/blob/master/ssh_note/ssh_ch14/Spring%20Data%20JPA.markdown
https://www.bilibili.com/video/BV1Jf4y1m7YT
https://www.bilibili.com/video/BV1FC4y1W7Zv
http://www.iocoder.cn/Spring-Data-JPA/good-collection/
http://www.dewafer.com/2016/05/09/reading-src-of-spring-data-jpa/
http://www.reibang.com/p/d05ba90d19e7