日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

02 | Spring Data Common 之 Repository 如何全面掌握?

發布時間:2024/10/6 javascript 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 02 | Spring Data Common 之 Repository 如何全面掌握? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

通過上一課時,我們知道了 Spring Data 對整個數據操作做了很好的封裝,其中 Spring Data Common 定義了很多公用的接口和一些相對數據操作的公共實現(如分頁排序、結果映射、Autiting 信息、事務等),而 Spring Data JPA 就是 Spring Data Common 的關系數據庫的查詢實現。

所以本課時我們來了解一下 Spring Data Common 的核心內容——Repository。我將從 Repository 的所有子類著手,帶領你逐步掌握 CrudRepository、PageingAndSortingRepository、JpaRepository的使用。

在講解 Repository 之前,我們先來看看 Spring Data JPA 所依賴的 jar 包關系是什么樣的,看下 Spring Data Common 的 jar 依賴關系。

Spring Data Common 的依賴關系
我們通過 Gradle 看一下項目依賴,了解一下 Spring Data Common 的依賴關系。

Resposiory 是 Spring Data 里面進行數據庫操作頂級的抽象接口,里面什么方法都沒有,但是如果任何接口繼承它,就能得到一個 Repository,還可以實現 JPA 的一些默認實現方法。Spring 利用 Respository 作為 DAO 操作的 Type,以及利用 Java 動態代理機制就可以實現很多功能,比如為什么接口就能實現 DB 的相關操作?這就是 Spring 框架的高明之處。

Spring 在做動態代理的時候,只要是它的子類或者實現類,再利用 T 類以及 T 類的 主鍵 ID 類型作為泛型的類型參數,就可以來標記出來、并捕獲到要使用的實體類型,就能幫助使用者進行數據庫操作。

Repository 類層次關系
下面我們來根據存這個基類 Repository 接口,順藤摸瓜看看 Spring Data JPA 里面都有什么。

首先,我們用工具 Intellij Idea,打開類 Repository.class,然后依次導航 → Hierchy 類型,會得到如下圖所示的結果:

通過該層次結構視圖,你就會明白基類 Repository 的用意,由此可知,存儲庫分為以下 4 個大類。

  • ReactiveCrudRepository 這條線是響應式編程,主要支持當前 NoSQL 方面的操作,因為這方面大部分操作都是分布式的,所以由此我們可以看出 Spring Data 想統一數據操作的“野心”,即想提供關于所有 Data 方面的操作。目前 Reactive 主要有 Cassandra、MongoDB、Redis 的實現。

  • RxJava2CrudRepository 這條線是為了支持 RxJava 2 做的標準響應式編程的接口。

  • CoroutineCrudRepository 這條繼承關系鏈是為了支持 Kotlin 語法而實現的。

  • CrudRepository 這條繼承關系鏈正是本課時我要詳細介紹的 JPA 相關的操作接口,你也可以把我的這種方法應用到另外 3 種繼承關系鏈里面學習。

然后,通過 Intellij Idea,我們也可以打開類 UserRepository.java(第一課時“Spring Data JPA 初識”里面的案例),在此類里面,鼠標右鍵點擊 Show Diagram 顯示層次結構圖,用圖表的方式查看類的關系層次,打開后如下圖(Repository 繼承關系圖)所示:

在這里簡單介紹一下,我們需要掌握和使用到的類如下所示。

7 個大 Repository 接口:

  • Repository(org.springframework.data.repository),沒有暴露任何方法;

  • CrudRepository(org.springframework.data.repository),簡單的 Curd 方法;

  • PagingAndSortingRepository(org.springframework.data.repository),帶分頁和排序的方法;

  • QueryByExampleExecutor(org.springframework.data.repository.query),簡單 Example 查詢;

  • JpaRepository(org.springframework.data.jpa.repository),JPA 的擴展方法;

  • JpaSpecificationExecutor(org.springframework.data.jpa.repository),JpaSpecification 擴展查詢;

  • QueryDslPredicateExecutor(org.springframework.data.querydsl),QueryDsl 的封裝。

兩大 Repository 實現類:

  • SimpleJpaRepository(org.springframework.data.jpa.repository.support),JPA 所有接口的默認實現類;

  • QueryDslJpaRepository(org.springframework.data.jpa.repository.support),QueryDsl 的實現類。

關于其他的類,后面我也會通過不同方式的講解,讓你一一認識。下面我們再來看一個 Repository 實例。

import org.springframework.data.repository.Repository;import java.util.List;public interface UserRepository extends Repository<User,Integer> {//根據名稱進行查詢用戶列表List<User> findByName(String name);// 根據用戶的郵箱和名稱查詢List<User> findByEmailAndName(String email, String name);}

由于 Repository 接口里面沒有任何方法,所以此 UserRepository 對外只有兩個可用方法,如上面的代碼一樣。Service 里面只能調用到 findByName 和 findByEmailAndName 兩個方法,我們通過 IDEA 的 Structure 也可以看到對外只有兩個方法可用,如下所示:

這時,我在第 01 課時中“Spring Boot 和 Spring Data JPA 的 Demo 演示”的例子里,提到過的 Controller 中引用 userRepository 的 save 和 findAll 方法就會報錯。

上面這個實例通過繼承 Repository,使 Spring 容器知道 UserRepository 是 DB 操作的類,是我們可以對 User 對象進行 CURD 的操作。這時我們對 Repository 有了一定的掌握,接下來再來看看它的直接子類 CurdRepository 接口都為我們提供了哪些方法。

CrudRepository 接口

下面我們通過 IDEA 工具,看下 CrudRepository 為我們提供的方法有哪些。

通過上圖,你可以看到其中展示的一些方法,在這里一一說明一下:

  • count(): long 查詢總數返回 long 類型;

  • void delete(T entity) 根據 entity 進行刪除;

  • void deleteAll(Iterable<? extends T> entities) 批量刪除;

  • void deleteAll() 刪除所有;原理可以通過剛才的類關系查看,CrudRepository 的實現方法如下:

//SimpleJpaRepository里面的deleteALL方法public void deleteAll() {for (T element : findAll()) {delete(element);}}

通過源碼我們可以看出 SimpleJpaRepository 里面的 deleteAll 是利用 for 循環調用 delete 方法進行刪除操作。我們接著看 CrudRepository 提供的方法。

  • void deleteById(ID id); 根據主鍵刪除,查看源碼會發現,其是先查詢出來再進行刪除;

  • boolean existsById(ID id) 根據主鍵判斷實體是否存在;

  • Iterable findAllById(Iterable ids); 根據主鍵列表查詢實體列表;

  • Iterable findAll(); 查詢實體的所有列表;

  • Optional findById(ID id); 根據主鍵查詢實體,返回 JDK 1.8 的 Optional,這可以避免 null exception;

  • <‘S’ extends T> S save(S entity); 保存實體方法,參數和返回結果可以是實體的子類;

  • saveAll(Iterable entities) : 批量保存,原理和 save方法相同,我們去看實現的話,就是 for 循環調用上面的 save 方法。

上面這些方法是 CrudRepository 對外暴露的常見的 Crud 接口,我們在對數據庫進行 Crud 的時候就會運用到,如我們打算對 User 實體進行 Curd 操作,來看一下應該怎么寫,如下所示:

public interface UserRepository extends CrudRepository<User,Long> { }


這里我們需要注意一下 save 和 deleteById 的實現邏輯,分別看看一下這兩種方法是怎么實現的:

//新增或者保存public <S extends T> S save(S entity) {if (entityInformation.isNew(entity)) {em.persist(entity);return entity;} else {return em.merge(entity);}}//刪除public void deleteById(ID id) {Assert.notNull(id, ID_MUST_NOT_BE_NULL);delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));}

你會發現在進行 Update、Delete、Insert 等操作之前,我們看上面的源碼,會通過 findById 先查詢一下實體對象的 ID,然后再去對查詢出來的實體對象進行保存操作。而如果在 Delete 的時候,查詢到的對象不存在,則直接拋異常。

我在這里特別強調了一下 Delete 和 Save 方法,是因為在實際工作中,看到有的同事畫蛇添足:自己在做 Save 的時候先去 Find 一下,其實是沒有必要的,Spring JPA 底層都考慮到了。這里其實是想告訴你,當我們用任何第三方方法的時候,最好先查一下其源碼和邏輯或者 API,然后再寫出優雅的代碼。

關于 entityInformation.isNew(entity),在這里簡單說一下,如果當傳遞的參數里面沒有 ID,則直接 insert;若當傳遞的參數里面有 ID,則會觸發 select 查詢。此方法會去看一下數據庫里面是否存在此記錄,若存在,則 update,否則 insert。后面第 14 課時講樂觀鎖實現機制的時候會有詳細介紹。

PagingAndSortingRepository 接口

上面我們介紹完了 Crud 的基本操作,發現沒有分頁和排序方法,那么接下來講講 PagingAndSortingRepository 接口,該接口也是 Repository 接口的子類,主要用于分頁查詢和排序查詢。我們先來看看 PagingAndSortingRepository 的源碼,了解一下都有哪些方法。

PagingAndSortingRepository 的源碼

PagingAndSortingRepository 源碼發現有兩個方法,分別是用于分頁和排序的時候使用的,如下所示:

package org.springframework.data.repository;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.domain.Sort;@NoRepositoryBeanpublic interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {Iterable<T> findAll(Sort sort);1)Page<T> findAll(Pageable pageable);2}

其中,第一個方法 findAll 參數是 Sort,是根據排序參數,實現不同的排序規則獲取所有的對象的集合;第二個方法 findAll 參數是 Pageable,是根據分頁和排序進行查詢,并用 Page 對返回結果進行封裝。而 Pageable 對象包含 Page 和 Sort 對象。

通過開篇講到的【Repository 繼承關系圖】和上面介紹的一大堆源碼可以看到,PagingAndSortingRepository 繼承了 CrudRepository,進而擁有了父類的方法,并且增加了分頁和排序等對查詢結果進行限制的通用的方法。

PagingAndSortingRepository 和 CrudRepository 都是 Spring Data Common 的標準接口,那么實現類是什么呢?如果我們采用 JPA,那對應的實現類就是 Spring Data JPA 的 jar 包里面的 SimpleJpaRepository。如果是其他 NoSQL的 實現如 MongoDB,那實現就在 Spring Data MongoDB 的 jar 里面的 MongoRepositoryImpl。

關于 PagingAndSortingRepository 源碼的介紹到這里,下面我們看看怎么使用這兩個方法。

PagingAndSortingRepository 使用案例

第一步:我們定一個 UserRepository 類來繼承 PagingAndSortingRepository 接口,實現對 User 的分頁和排序操作,實現源碼如下:

package com.example.jpa.example1;import org.springframework.data.repository.PagingAndSortingRepository;public interface UserRepository extends PagingAndSortingRepository<User,Long> {}

第二步:我們利用 UserRepository 直接繼承 PagingAndSortingRepository 即可,而 Controller 里面就可以有如下用法了:

/*** 驗證排序和分頁查詢方法,Pageable的默認實現類:PageRequest* @return*/@GetMapping(path = "/page")@ResponseBodypublic Page<User> getAllUserByPage() {return userRepository.findAll(PageRequest.of(1, 20,Sort.by(new Sort.Order(Sort.Direction.ASC,"name"))));}/*** 排序查詢方法,使用Sort對象* @return*/@GetMapping(path = "/sort")@ResponseBodypublic Iterable<User> getAllUsersWithSort() {return userRepository.findAll(Sort.by(new Sort.Order(Sort.Direction.ASC,"name")));}

到這里,你已經實現了對實體 User 的 DB 操作,那么以上內容我們學習了 CURD 和分頁排序的基本操作,下面看看 JpaRepsitory 的接口為我們提供了哪些方法。

JpaRepository 接口

到這里可以進入到分水嶺了,上面的那些都是 Spring Data 為了兼容 NoSQL 而進行的一些抽象封裝,而從 JpaRepository 開始是對關系型數據庫進行抽象封裝。從類圖可以看出來它繼承 PagingAndSortingRepository 類,也就繼承了其所有方法,并且其實現類也是 SimpleJpaRepository。從類圖上還可以看出 JpaRepository 繼承和擁有了 QueryByExampleExecutor 的相關方法,我們先來看一下 JpaRepository 有哪些方法。一樣的道理,我們直接看它的源碼,看 Structure 即可,如下圖所示:

涉及 QueryByExample 的部分我們在 11 課時“JpaRepository 如何自定義”再詳細介紹,而 JpaRepository 里面重點新增了批量刪除,優化了批量刪除的性能,類似于之前 SQL 的 batch 操作,并不是像上面的 deleteAll 來 for 循環刪除。其中 flush() 和 saveAndFlush() 提供了手動刷新 session,把對象的值立即更新到數據庫里面的機制。

我們都知道 JPA 是 由 Hibernate 實現的,所以有 session 一級緩存的機制,當調用 save() 方法的時候,數據庫里面是不會立即變化的,其原理我將在 21 課時“Persistence Context 所表達的核心概念是什么”再詳細講解。JpaRepository 的使用方式也一樣,直接繼承 JpaRepository 即可。

我們看一個 Demo,用 UserRepository 直接繼承 JpaRepository,來實現 JPA 的相關方法,如下所示:

public interface UserRepository extends JpaRepository<User,Long> {}

這樣 controller 里面就可以直接調用 JpaRepository 及其父接口里面的所有方法了。

那么以上就是我們對 Repository 及其他子接口的使用案例,在應用時,你需要注意不同的接口有不同的方法,根據業務場景繼承不同的接口即可。下面我們接著學習 Repository 的實現類 SimpleJpaRepository。

Repository 的實現類 SimpleJpaRepository

關系數據庫的所有 Repository 接口的實現類就是 SimpleJpaRepository,如果有些業務場景需要進行擴展了,可以繼續繼承此類,如 QueryDsl 的擴展(雖然不推薦使用了,但我們可以參考它的做法,自定義自己的 SimpleJpaRepository),如果能將此類里面的實現方法看透了,基本上 JPA 中的 API 就能掌握大部分內容。

我們可以通過 Debug 視圖看一下動態代理過程,如下面【類的繼承關系圖】所示:

你會發現 UserRepository 的實現類是 Spring 啟動的時候,利用 Java 動態代理機制幫我們生成的實現類,而真正的實現類就是 SimpleJpaRepository。

通過上面【類的繼承關系圖】也可以知道 SimpleJpaRepository 是 Repository 接口、CrudRepository 接口、PagingAndSortingRepository 接口、JpaRepository 接口的實現。其中,SimpleJpaRepository 的部分源碼如下:

@Repository@Transactional(readOnly = true)public class SimpleJpaRepository<T, ID> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!";private final JpaEntityInformation<T, ?> entityInformation;private final EntityManager em;private final PersistenceProvider provider;private @Nullable CrudMethodMetadata metadata;......@Transactionalpublic void deleteAllInBatch() {em.createQuery(getDeleteAllQueryString()).executeUpdate();}......

通過此類的源碼,我們可以挺清晰地看出 SimpleJpaRepository 的實現機制,是通過 EntityManger 進行實體的操作,而 JpaEntityInforMation 里面存在實體的相關信息和 Crud 方法的元數據等。

上面我們講到利用 Java 動態代理機制幫我們生成的實現類,那么關于動態代理的實現,我們可以在 RepositoryFactorySupport 設置一個斷點,啟動的時候,在我們的斷點處就會發現 UserRepository 的接口會被動態代理成 SimpleJapRepository 的實現,如下圖所示:

這里需要注意的是每一個 Repository 的子類,都會通過這里的動態代理生成實現類。

Repository 接口給我的啟發

在接觸了 Repository 的源碼之后,我在工作中遇到過一些類似需要抽象接口和寫動態代理的情況,所以對于 Repository 的源碼,我受到了一些啟發:

第一,上面的 7 個大 Repository 接口,我們在使用的時候可以根據實際場景,來繼承不同的接口,從而選擇暴露不同的 Spring Data Common 給我們提供的已有接口。這其實利用了 Java 語言的 interface 特性,在這里可以好好理解一下 interface 的妙用。

第二,利用源碼也可以很好地理解一下 Spring 中動態代理的作用,可以利用這種思想,在改善 MyBatis 的時候使用。

總結
本課時到這里就結束了,這一課時我講解了 Repository 接口、CrudRepository 接口、PagingAndSortingRepository 接口、JpaRepository 接口的用法,通過源碼我們知道了接口里面的方法有哪些、怎么實現的,也知道了 Spring 的動態代理機制是怎么運用到 UserRepository 接口的。

通過這一課時,相信你對 Repository 的基本用法,以及接口暴露的方法和使用方法都有了一定的了解,下節課我會講解除了 Repository 的接口里面定義的方法之外,還可以在我們的 UserRepository 里面實現哪些方法,又會有哪些動態實現機制呢?我們到時見。

源碼位置
spring-data-jp

總結

以上是生活随笔為你收集整理的02 | Spring Data Common 之 Repository 如何全面掌握?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。