spring-boot-starter-data-jpa源碼解析


public interface UserRepository extends JpaRepository<User, Long> { User findByUserName(String userName); }


 

userRepository被注入了一個動態代理,被代理的類是JpaRepository的一個實現SimpleJpaRespositry

繼續往下debug,在進到findByUserName方法的時候,發現被上文提到的JdkDynamicAopProxy捕獲,然后經過一系列的方法攔截,最終進到QueryExecutorMethodInterceptor.doInvoke中。這個攔截器主要做的事情就是判斷方法類型,然后執行對應的操作.
我們的findByUserName屬於自定義查詢,於是就進入了查詢策略對應的execute方法。在執行execute時,會先選取對應的JpaQueryExecution,調用AbtractJpaQuery.getExecution()

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 method.getClearAutomatically() ? new ModifyingExecution(method, em) : new ModifyingExecution(method, null); } else { return new SingleEntityExecution(); } }

如上述代碼所示,根據method變量實例化時的查詢設置方式,實例化不同的JpaQueryExecution子類實例去運行。我們的findByUserName最終落入了SingleEntityExecution —— 返回單個實例的 Execution。繼續跟蹤execute方法,發現底層使用了 hibernate 的 CriteriaQueryImpl 完成了sql的拼裝,這里就不做贅述了。

再來看看這類的method。在 spring-data-jpa 中,JpaQueryMethod就是Repository接口中帶有@Query注解方法的全部信息,包括注解,類名,實參等的存儲類,所以Repository接口有多少個@Query注解方法,就會包含多少個JpaQueryMethod實例被加入監聽序列。實際運行時,一個RepositoryQuery實例持有一個JpaQueryMethod實例,JpaQueryMethod又持有一個Method實例。

再來看看RepositoryQuery,在QueryExecutorMethodInterceptor中維護了一個Map<Method, RepositoryQuery> queriesRepositoryQuery的直接抽象子類是AbstractJpaQuery,可以看到,一個RepositoryQuery實例持有一個JpaQueryMethod實例,JpaQueryMethod又持有一個Method實例,所以RepositoryQuery實例的用途很明顯,一個RepositoryQuery代表了Repository接口中的一個方法,根據方法頭上注解不同的形態,將每個Repository接口中的方法分別映射成相對應的RepositoryQuery實例。

下面我們就來看看spring-data-jpa對RepositoryQuery實例的具體分類: 
1.SimpleJpaQuery 
方法頭上@Query注解的nativeQuery屬性缺省值為false,也就是使用JPQL,此時會創建SimpleJpaQuery實例,並通過兩個StringQuery類實例分別持有query jpql語句和根據query jpql計算拼接出來的countQuery jpql語句;

2.NativeJpaQuery 
方法頭上@Query注解的nativeQuery屬性如果顯式的設置為nativeQuery=true,也就是使用原生SQL,此時就會創建NativeJpaQuery實例;

3.PartTreeJpaQuery 
方法頭上未進行@Query注解,將使用spring-data-jpa獨創的方法名識別的方式進行sql語句拼接,此時在spring-data-jpa內部就會創建一個PartTreeJpaQuery實例;

4.NamedQuery 
使用javax.persistence.NamedQuery注解訪問數據庫的形式,此時在spring-data-jpa內部就會根據此注解選擇創建一個NamedQuery實例;

5.StoredProcedureJpaQuery 
顧名思義,在Repository接口的方法頭上使用org.springframework.data.jpa.repository.query.Procedure注解,也就是調用存儲過程的方式訪問數據庫,此時在spring-data-jpa內部就會根據@Procedure注解而選擇創建一個StoredProcedureJpaQuery實例。

那么問題來了,sql 拼接的時候怎么知道是根據userName進行查詢呢?是取自方法名中的 byUsername 還是方法參數 userName 呢? spring 具體是在什么時候知道查詢參數的呢 ?

數據如何注入

spring 在啟動的時候會實例化一個 Repositories,它會去掃描所有的 class,然后找出由我們定義的、繼承自org.springframework.data.repository.Repositor的接口,然后遍歷這些接口,針對每個接口依次創建如下幾個實例:

  1. SimpleJpaRespositry —— 用來進行默認的 DAO 操作,是所有 Repository 的默認實現
  2. JpaRepositoryFactoryBean —— 裝配 bean,裝載了動態代理 Proxy,會以對應的 DAO 的 beanName 為 key 注冊到DefaultListableBeanFactory中,在需要被注入的時候從這個 bean 中取出對應的動態代理 Proxy 注入給 DAO
  3. JdkDynamicAopProxy —— 動態代理對應的InvocationHandler,負責攔截 DAO 接口的所有的方法調用,然后做相應處理,比如findByUsername被調用的時候會先經過這個類的 invoke 方法

JpaRepositoryFactoryBean.getRepository()方法被調用的過程中,還是在實例化QueryExecutorMethodInterceptor這個攔截器的時候,spring 會去為我們的方法創建一個PartTreeJpaQuery,在它的構造方法中同時會實例化一個PartTree對象。PartTree定義了一系列的正則表達式,全部用於截取方法名,通過方法名來分解查詢的條件,排序方式,查詢結果等等,這個分解的步驟是在進程啟動時加載 Bean 的過程中進行的,當執行查詢的時候直接取方法對應的PartTree用來進行 sql 的拼裝,然后進行 DB 的查詢,返回結果。

到此為止,我們整個JpaRepository接口相關的鏈路就算走通啦,簡單的總結如下:
spring 會在啟動的時候掃描所有繼承自 Repository 接口的 DAO 接口,然后為其實例化一個動態代理,同時根據它的方法名、參數等為其裝配一系列DB操作組件,在需要注入的時候為對應的接口注入這個動態代理,在 DAO 方法被調用的時會走這個動態代理,然后經過一系列的方法攔截路由到最終的 DB 操作執行器JpaQueryExecution,然后拼裝 sql,執行相關操作,返回結果。

應用

基本查詢

基本查詢分為兩種,一種是 spring data 默認已經實現(只要繼承JpaRepository),一種是根據查詢的方法來自動解析成 SQL。

預先生成

public interface UserRepository extends JpaRepository<User, Long> { } @Test public void testBaseQuery() throws Exception { User user=new User(); userRepository.findAll(); userRepository.findOne(1l); userRepository.save(user); userRepository.delete(user); userRepository.count(); userRepository.exists(1l); // ... }

自定義簡單查詢

自定義的簡單查詢就是根據方法名來自動生成SQL,主要的語法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟屬性名稱,舉幾個例子:

User findByUserName(String userName); User findByUserNameOrEmail(String username, String email); Long deleteById(Long id); Long countByUserName(String userName); List<User> findByEmailLike(String email); User findByUserNameIgnoreCase(String userName); List<User> findByUserNameOrderByEmailDesc(String email);

具體的關鍵字,使用方法和生產成 SQL 如下表所示

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<age> ages)</age> … where x.age in ?1
NotIn findByAgeNotIn(Collection<age> age)</age> … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

復雜查詢

在實際的開發中我們需要用到分頁、刪選、連表等查詢的時候就需要特殊的方法或者自定義 SQL

分頁查詢

分頁查詢在實際使用中非常普遍了,spring data jpa已經幫我們實現了分頁的功能,在查詢的方法中,需要傳入參數Pageable
,當查詢中有多個參數的時候Pageable建議做為最后一個參數傳入。Pageable是 spring 封裝的分頁實現類,使用的時候需要傳入頁數、每頁條數和排序規則

Page<User> findALL(Pageable pageable); Page<User> findByUserName(String userName,Pageable pageable);
@Test
public void testPageQuery() throws Exception { int page=1,size=10; Sort sort = new Sort(Direction.DESC, "id"); Pageable pageable = new PageRequest(page, size, sort); userRepository.findALL(pageable); userRepository.findByUserName("testName", pageable); }

有時候我們只需要查詢前N個元素,或者支取前一個實體。

User findFirstByOrderByLastnameAsc(); User findTopByOrderByAgeDesc(); Page<User> queryFirst10ByLastname(String lastname, Pageable pageable); List<User> findFirst10ByLastname(String lastname, Sort sort); List<User> findTop10ByLastname(String lastname, Pageable pageable);

自定義SQL查詢

其實 Spring data 大部分的 SQL 都可以根據方法名定義的方式來實現,但是由於某些原因我們想使用自定義的 SQL 來查詢,spring data 也是完美支持的;在 SQL 的查詢方法上面使用 @Query 注解,如涉及到刪除和修改在需要加上 @Modifying 。也可以根據需要添加 @Transactional 對事物的支持,查詢超時的設置等

@Modifying @Query("update User u set u.userName = ?1 where c.id = ?2") int modifyByIdAndUserId(String userName, Long id); @Transactional @Modifying @Query("delete from User where id = ?1") void deleteByUserId(Long id); @Transactional(timeout = 10) @Query("select u from User u where u.emailAddress = ?1") User findByEmailAddress(String emailAddress);

多表查詢

多表查詢在 spring data jpa 中有兩種實現方式,第一種是利用 hibernate 的級聯查詢來實現,第二種是創建一個結果集的接口來接收連表查詢后的結果,這里介紹第二種方式。

首先需要定義一個結果集的接口類。

public interface HotelSummary { City getCity(); String getName(); Double getAverageRating(); default Integer getAverageRatingRounded() { return getAverageRating() == null ? null : (int) Math.round(getAverageRating()); } }

查詢的方法返回類型設置為新創建的接口

@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating from Hotel h left outer join h.reviews r where h.city = ?1 group by h") Page<HotelSummary> findByCity(City city, Pageable pageable); @Query("select h.name as name, avg(r.rating) as averageRating from Hotel h left outer join h.reviews r group by h") Page<HotelSummary> findByCity(Pageable pageable);
Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name")); for(HotelSummary summay:hotels){ System.out.println("Name" +summay.getName()); }

在運行中 Spring 會給接口(HotelSummary)自動生產一個代理類來接收返回的結果,代碼會使用 getXX 的形式來獲取

和 mybatis 的比較

spring data jpa 底層采用 hibernate 做為 ORM 框架,所以 spring data jpa 和 mybatis 的比較其實就是 hibernate 和 mybatis 的比較。下面從幾個方面來對比下兩者

基本概念

從基本概念和框架目標上看,兩個框架差別還是很大的。hibernate 是一個自動化更強、更高級的框架,畢竟在java代碼層面上,省去了絕大部分 sql 編寫,取而代之的是用面向對象的方式操作關系型數據庫的數據。而 MyBatis 則是一個能夠靈活編寫 sql 語句,並將 sql 的入參和查詢結果映射成 POJOs 的一個持久層框架。所以,從表面上看,hibernate 能方便、自動化更強,而 MyBatis 在 Sql 語句編寫方面則更靈活自由。

性能

正如上面介紹的, Hibernate 比 MyBatis 抽象封裝的程度更高,理論上單個語句之心的性能會低一點(所有的框架都是一樣,排除算法上的差異,越是底層,執行效率越高)。

但 Hibernate 會設置緩存,對於重復查詢有一定的優化,而且從編碼效率來說,Hibernate 的編碼效果肯定是會高一點的。所以,從整體的角度來看性能的話,其實兩者不能完全說誰勝誰劣。

ORM

Hibernate 是完備的 ORM 框架,是符合 JPA 規范的, MyBatis 沒有按照JPA那套規范實現。目前 Spring 以及 Spring Boot 官方都沒有針對 MyBatis 有具體的支持,但對 Hibernate 的集成一直是有的。但這並不是說 mybatis 和 spring 無法集成,MyBatis 官方社區自身也是有 對 Spring,Spring boot 集成做支持的,所以在技術上,兩者都不存在問題。

總結

總結下 mybatis 的優點:

  • 簡單易學
  • 靈活,MyBatis不會對應用程序或者數據庫的現有設計強加任何影響。 注解或者使用 SQL 寫在 XML 里,便於統一管理和優化。通過 SQL 基本上可以實現我們不使用數據訪問框架可以實現的所有功能,或許更多。
  • 解除 SQL 與程序代碼的耦合,SQL 和代碼的分離,提高了可維護性。
  • 提供映射標簽,支持對象與數據庫的 ORM 字段關系映射。
  • 提供對象關系映射標簽,支持對象關系組建維護。
  • 提供XML標簽,支持編寫動態SQL。

hibernate 的優點:
JPA 的宗旨是為 POJO 提供持久化標准規范,實現使用的 Hibernate,Hibernate 是一個全自動的持久層框架,並且提供了面向對象的 SQL 支持,不需要編寫復雜的 SQL 語句,直接操作 Java 對象即可,從而大大降低了代碼量,讓即使不懂 SQL 的開發人員,也使程序員更加專注於業務邏輯的實現。對於關聯查詢,也僅僅是使用一些注解即可完成一些復雜的 SQL功能。

最后再做一個簡單的總結:

  • 如果能有很好的數據庫規范的話,使用這兩個哪個都不會差
  • 如果有能力並且特別想掌控 SQL,那就選 MyBaits,否則就依賴 JPA 的魔力來快速完成業務開發
  • 個人認為兩者最本質的不同點,hibernate 的理念是面向對象,mybatis 的理念是面向過程,類似於 JAVA 和 PYTHON。當然,用hibernate也可以寫出面向關系代碼和系統,但卻得不到面向關系的各種好處,最大的便是編寫 sql 的靈活性,同時也失去面向對象意義和好處——一句話,不倫不類。那么,面向對象和關系型模型有什么不同,體現在哪里呢?實際上兩者要面對的領域和要解決的問題是根本不同的:面向對象致力於解決計算機邏輯問題,而關系模型致力於解決數據的高效存取問題。我們不妨對比一下面向對象的概念原則和關系型數據庫的不同之處:面向對象考慮的是對象的整個生命周期包括在對象的創建、持久化、狀態的改變和行為等,對象的持久化只是對象的一種狀態,而面向關系型數據庫的概念則更關注數據的高效存儲和讀取;面向對象更強調對象狀態的封裝性,對象封裝自己的狀態(或數據)不允許外部對象隨意修改,只暴露一些合法的行為方法供外部對象調用;而關系型數據庫則是開放的,可以供用戶隨意讀取和修改關系,並可以和其他表任意的關聯(只要sql正確允許的情況下);面向對象試圖為動態的世界建模,他要描述的是世界的過程和規律,進而適應發展和變化,面向對象總是在變化中處理各種各樣的變化。而關系型模型為靜態世界建模,它通過數據快照記錄了世界在某一時候的狀態,它是靜態的。從上面兩者基本概念和思想的對比來看,可以得出結論hibernate和MyBatis兩個框架的側重點完全不同。所以我們就兩個框架選擇上,就需要根據不同的項目需求選擇不同的框架。在框架的使用中,也要考慮考慮框架的優勢和劣勢,揚長避短,發揮出框架的最大效用,才能真正的提高項目研發效率、完成項目的目標。但相反,如果使用Spring Data JPA和hibernate等ORM的框架而沒有以面向對象思想和方法去分析和設計系統,而是抱怨框架不能靈活操作sql查詢數據,那就是想讓狗去幫你拿耗子了。

文章轉自:
http://www.cnblogs.com/bodhitree/p/9468585.html




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM