Spring data jpa 復雜動態查詢方式總結


一.Spring data jpa 簡介

首先我並不推薦使用jpa作為ORM框架,畢竟對於負責查詢的時候還是不太靈活,還是建議使用mybatis,自己寫sql比較好.但是如果公司用這個就沒辦法了,可以學習一下,對於簡單查詢還是非常好用的.

    首先JPA是Java持久層API,由Sun公司開發, 希望整合ORM技術,實現天下歸一.  誕生的緣由是為了整合第三方ORM框架,建立一種標准的方式,目前也是在按照這個方向發展,但是還沒能完全實現。在ORM框架中,Hibernate是一支很大的部隊,使用很廣泛,也很方便,能力也很強,同時Hibernate也是和JPA整合的比較良好,我們可以認為JPA是標准,事實上也是,JPA幾乎都是接口,實現都是Hibernate在做,宏觀上面看,在JPA的統一之下Hibernate很良好的運行。

    Spring-data-jpa,Spring與jpa的整合

    Spring主要是在做第三方工具的整合 不重新造輪子. 而在與第三方整合這方面,Spring做了持久化這一塊的工作,於是就有了Spring-data-**這一系列包。包括,Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis

    Spring-data-jpa,目的少使用sql

    我們都知道,在使用持久化工具的時候,一般都有一個對象來操作數據庫,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通過這個對象來操作數據庫。我們一般按照三層結構來看的話,Service層做業務邏輯處理,Dao層和數據庫打交道,在Dao中,就存在着上面的對象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD(增刪改查),所有的基礎CRUD框架都提供,我們使用起來感覺很方便,很給力,業務邏輯層面的處理ORM是沒有提供的,如果使用原生的框架,業務邏輯代碼我們一般會自定義,會自己去寫SQL語句,然后執行。在這個時候,Spring-data-jpa的威力就體現出來了,ORM提供的能力他都提供,ORM框架沒有提供的業務邏輯功能Spring-data-jpa也提供,全方位的解決用戶的需求。使用Spring-data-jpa進行開發的過程中,常用的功能,我們幾乎不需要寫一條sql語句,至少在我看來,企業級應用基本上可以不用寫任何一條sql,當然spring-data-jpa也提供自己寫sql的方式

     返回值為對象的意義

    是jpa查詢表內容返回值基本上都是對象,但是僅僅需要一個字段返回整體對象不是會有很多數據冗余嗎,其實大多數情況對一個數據表的查詢不可能只有一次或者說這個表不僅僅是這一次會用到,如果我寫好一個返回對象的方法,之后都可以直接調用,一般情況下多出一點數據對網絡的壓力可以忽略不計,而這樣對開發效率的提升還是很大的.如果僅僅想得到一部分字段也可以新建一個只有想要字段的Entity.

 

二.Spring data jpa 基本使用

對於配置方法和基礎的dao層寫法等不做介紹,基礎篇僅當做一個方法字典.

    1.核心方法

  • 查詢所有數據 findAll()
  • 修改 添加數據  S save(S entity)
  • 分頁查詢 Page<S> findAll(Example<S> example, Pageable pageable)
  • 根據id查詢 findOne()
  • 根據實體類屬性查詢: findByProperty (type Property); 例如:findByAge(int age)
  • 刪除 void delete(T entity)
  • 計數 查詢 long count() 或者 根據某個屬性的值查詢總數 countByAge(int age)
  • 是否存在   boolean existsById(ID primaryKey)

   

    2.查詢關鍵字

-and

And 例如:findByUsernameAndPassword(String user, Striang pwd);

-or

Or 例如:findByUsernameOrAddress(String user, String addr);

-between

Between 例如:SalaryBetween(int max, int min);

-"<"

LessThan 例如: findBySalaryLessThan(int max);

-">"

GreaterThan 例如: findBySalaryGreaterThan(int min);

-is null

IsNull 例如: findByUsernameIsNull();

-is not null

IsNotNull NotNull 與 IsNotNull 等價 例如: findByUsernameIsNotNull();

-like

Like 例如: findByUsernameLike(String user);

-not like

NotLike 例如: findByUsernameNotLike(String user);

-order by

OrderBy 例如: findByUsernameOrderByNameAsc(String user);直接通過name正序排序

-"!="

Not 例如: findByUsernameNot(String user);

-in

In 例如: findByUsernameIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;

-not in

NotIn 例如: findByUsernameNotIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;

-Top/Limit

查詢方法結果的數量可以通過關鍵字來限制,first 或者 top都可以使用。top/first加數字可以指定要返回最大結果的大小 默認為1

     例如:


   
   
  
  
          
  1. User findFirstByOrderByLastnameAsc();
  2. User findTopByOrderByAgeDesc();
  3. Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
  4. Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
  5. List<User> findFirst10ByLastname(String lastname, Sort sort);
  6. List<User> findTop10ByLastname(String lastname, Pageable pageable);
  • 詳細查詢語法
關鍵詞 示例 對應的sql片段

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstname,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)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… 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)

 

    3.內置方法

  • Sort_排序

   
   
  
  
          
  1. Sort sort = new Sort(Sort.Direction.ASC, "id");
  2. //其中第一個參數表示是降序還是升序(此處表示升序)
  3. //第二個參數表示你要按你的 entity(記住是entity中聲明的變量,不是數據庫中表對應的字段)中的那個變量進行排序
  • PageRequest_分頁

   
   
  
  
          
  1. PageRequest pageRequest = new PageRequest(index, num, sort);
  2. //index偏移量 num查詢數量 sort排序

    分頁+排序實現:


   
   
  
  
          
  1. DemoBean demoBean = new DemoBean();
  2. demoBean.setAppId(appId); //查詢條件
  3. //創建查詢參數
  4. Example<DemoBean> example = Example.of(demoBean);
  5. //獲取排序對象
  6. Sort sort = new Sort(Sort.Direction.DESC, "id");
  7. //創建分頁對象
  8. PageRequest pageRequest = new PageRequest(index, num, sort);
  9. //分頁查詢
  10. return demoRepository.findAll(example, pageRequest).getContent();
  • Example_實例查詢

    創建一個ExampleMatcher對象,最后再用Example的of方法構造相應的Example對象並傳遞給相關查詢方法。我們看看Spring的例子。


   
   
  
  
          
  1. Person person = new Person();
  2. person.setFirstname( "Dave"); //Firstname = 'Dave'
  3. ExampleMatcher matcher = ExampleMatcher.matching()
  4. .withMatcher( "name", GenericPropertyMatchers.startsWith()) //姓名采用“開始匹配”的方式查詢
  5. .withIgnorePaths( "int"); //忽略屬性:是否關注。因為是基本類型,需要忽略掉
  6. Example<Person> example = Example.of(person, matcher); //Example根據域對象和配置創建一個新的ExampleMatcher

    ExampleMatcher用於創建一個查詢對象,上面的代碼就創建了一個查詢對象。withIgnorePaths方法用來排除某個屬性的查詢。withIncludeNullValues方法讓空值也參與查詢,就是我們設置了對象的姓,而名為空值.

 

1、概念定義:

    上面例子中,是這樣創建“實例”的:Example<Customer> ex = Example.of(customer, matcher);我們看到,Example對象,由customer和matcher共同創建。

    A、實體對象:在持久化框架中與Table對應的域對象,一個對象代表數據庫表中的一條記錄,如上例中Customer對象。在構建查詢條件時,一個實體對象代表的是查詢條件中的“數值”部分。如:要查詢名字是“Dave”的客戶,實體對象只能存儲條件值“Dave”。

    B、匹配器:ExampleMatcher對象,它是匹配“實體對象”的,表示了如何使用“實體對象”中的“值”進行查詢,它代表的是“查詢方式”,解釋了如何去查的問題。如:要查詢FirstName是“Dave”的客戶,即名以“Dave"開頭的客戶,該對象就表示了“以什么開頭的”這個查詢方式,如上例中:withMatcher("name", GenericPropertyMatchers.startsWith())

    C、實例:即Example對象,代表的是完整的查詢條件。由實體對象(查詢條件值)和匹配器(查詢方式)共同創建。

    再來理解“實例查詢”,顧名思義,就是通過一個例子來查詢。要查詢的是Customer對象,查詢條件也是一個Customer對象,通過一個現有的客戶對象作為例子,查詢和這個例子相匹配的對象。

 

2、特點及約束(局限性):

    A、支持動態查詢。即支持查詢條件個數不固定的情況,如:客戶列表中有多個過濾條件,用戶使用時在“地址”查詢框中輸入了值,就需要按地址進行過濾,如果沒有輸入值,就忽略這個過濾條件。對應的實現是,在構建查詢條件Customer對象時,將address屬性值置具體的條件值或置為null。

    B、不支持過濾條件分組。即不支持過濾條件用 or(或) 來連接,所有的過濾查件,都是簡單一層的用 and(並且) 連接。

    C、僅支持字符串的開始/包含/結束/正則表達式匹配 和 其他屬性類型的精確匹配。查詢時,對一個要進行匹配的屬性(如:姓名 name),只能傳入一個過濾條件值,如以Customer為例,要查詢姓“劉”的客戶,“劉”這個條件值就存儲在表示條件對象的Customer對象的name屬性中,針對於“姓名”的過濾也只有這么一個存儲過濾值的位置,沒辦法同時傳入兩個過濾值。正是由於這個限制,有些查詢是沒辦法支持的,例如要查詢某個時間段內添加的客戶,對應的屬性是 addTime,需要傳入“開始時間”和“結束時間”兩個條件值,而這種查詢方式沒有存兩個值的位置,所以就沒辦法完成這樣的查詢。

 

3、ExampleMatcher的使用 :

  • 一些問題:

(1)Null值的處理。當某個條件值為Null,是應當忽略這個過濾條件呢,還是應當去匹配數據庫表中該字段值是Null的記錄?
(2)基本類型的處理。如客戶Customer對象中的年齡age是int型的,當頁面不傳入條件值時,它默認是0,是有值的,那是否參與查詢呢?
(3)忽略某些屬性值。一個實體對象,有許多個屬性,是否每個屬性都參與過濾?是否可以忽略某些屬性?
(4)不同的過濾方式。同樣是作為String值,可能“姓名”希望精確匹配,“地址”希望模糊匹配,如何做到?

(5)大小寫匹配。字符串匹配時,有時可能希望忽略大小寫,有時則不忽略,如何做到?

  • 一些方法:

1、關於基本數據類型。
實體對象中,避免使用基本數據類型,采用包裝器類型。如果已經采用了基本類型,

而這個屬性查詢時不需要進行過濾,則把它添加到忽略列表(ignoredPaths)中。

2、Null值處理方式。

默認值是 IGNORE(忽略),即當條件值為null時,則忽略此過濾條件,一般業務也是采用這種方式就可滿足。當需要查詢數據庫表中屬性為null的記錄時,可將值設為INCLUDE,這時,對於不需要參與查詢的屬性,都必須添加到忽略列表(ignoredPaths)中,否則會出現查不到數據的情況。

3、默認配置、特殊配置。

默認創建匹配器時,字符串采用的是精確匹配、不忽略大小寫,可以通過操作方法改變這種默認匹配,以滿足大多數查詢條件的需要,如將“字符串匹配方式”改為CONTAINING(包含,模糊匹配),這是比較常用的情況。對於個別屬性需要特定的查詢方式,可以通過配置“屬性特定查詢方式”來滿足要求。

4、非字符串屬性

如約束中所談,非字符串屬性均采用精確匹配,即等於。

5、忽略大小寫的問題。

忽略大小的生效與否,是依賴於數據庫的。例如 MySql 數據庫中,默認創建表結構時,字段是已經忽略大小寫的,所以這個配置與否,都是忽略的。如果業務需要嚴格區分大小寫,可以改變數據庫表結構屬性來實現,具體可百度。

  • 一些例子:

綜合使用:


   
   
  
  
          
  1. //創建查詢條件數據對象
  2. Customer customer = new Customer();
  3. customer.setName( "zhang");
  4. customer.setAddress( "河南省");
  5. customer.setRemark( "BB");
  6. //創建匹配器,即如何使用查詢條件
  7. ExampleMatcher matcher = ExampleMatcher.matching() //構建對象
  8. .withStringMatcher(StringMatcher.CONTAINING) //改變默認字符串匹配方式:模糊查詢
  9. .withIgnoreCase( true) //改變默認大小寫忽略方式:忽略大小寫
  10. .withMatcher( "address", GenericPropertyMatchers.startsWith()) //地址采用“開始匹配”的方式查詢
  11. .withIgnorePaths( "focus"); //忽略屬性:是否關注。因為是基本類型,需要忽略掉
  12. //創建實例
  13. Example<Customer> ex = Example.of(customer, matcher);
  14. //查詢
  15. List<Customer> ls = dao.findAll(ex);

查詢null值:


   
   
  
  
          
  1.      //創建查詢條件數據對象
  2. Customer customer = new Customer();
  3. //創建匹配器,即如何使用查詢條件
  4. ExampleMatcher matcher = ExampleMatcher.matching() //構建對象
  5. .withIncludeNullValues() //改變“Null值處理方式”:包括
  6. .withIgnorePaths( "id", "name", "sex", "age", "focus", "addTime", "remark", "customerType"); //忽略其他屬性
  7. //創建實例
  8. Example<Customer> ex = Example.of(customer, matcher);
  9. //查詢
  10. List<Customer> ls = dao.findAll(ex);

 

三.Spring data jpa 注解

1.Repository注解

@Modifying //做update操作時需要添加

@Query // 自定義Sql


   
   
  
  
          
  1. @Query(value = "SELECT * FROM USERS WHERE X = ?1", nativeQuery = true)
  2. User findByEmailAddress(String X);
  3. @Query( "select u from User u where u.firstname = :firstname") //不加nativeQuery應使用HQL
  4. User findByLastnameOrFirstname(@Param("lastname") String lastname);

@Transactional //事務

@Async //異步操作

 

2.Entity注解


   
   
  
  
          
  1. @Entity //不寫@Table默認為user
  2. @Table(name= "t_user") //自定義表名
  3. public class user {
  4. @Id //主鍵
  5. @GeneratedValue(strategy = GenerationType.AUTO) //采用數據庫自增方式生成主鍵
  6. //JPA提供的四種標准用法為TABLE,SEQUENCE,IDENTITY,AUTO.
  7. //TABLE:使用一個特定的數據庫表格來保存主鍵。
  8. //SEQUENCE:根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列。
  9. //IDENTITY:主鍵由數據庫自動生成(主要是自動增長型)
  10. //AUTO:主鍵由程序控制。
  11. @Transient //此字段不與數據庫關聯
  12. @Version //此字段加上樂觀鎖
  13. //字段為name,不允許為空,用戶名唯一
  14. @Column(name = "name", unique = true, nullable = false)
  15. private String name;
  16. @Temporal(TemporalType.DATE) //生成yyyy-MM-dd類型的日期
  17. //出參時間格式化
  18. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  19. //入參時,請求報文只需要傳入yyyymmddhhmmss字符串進來,則自動轉換為Date類型數據
  20. @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
  21. private Date createTime;
  22. public String getName() {
  23. return name;
  24. }
  25. public void setName(String name) {
  26. this.name = name;
  27. }
  28. }

 

四.繼承JpaSpecificationExecutor接口進行復雜查詢

spring data jpa 通過創建方法名來做查詢,只能做簡單的查詢,那如果我們要做復雜一些的查詢呢,多條件分頁怎么辦,這里,spring data jpa為我們提供了JpaSpecificationExecutor接口,只要簡單實現toPredicate方法就可以實現復雜的查詢

參考:https://www.cnblogs.com/happyday56/p/4661839.html

1.首先讓我們的接口繼承於JpaSpecificationExecutor


   
   
  
  
          
  1. public interface TaskDao extends JpaSpecificationExecutor<Task>{
  2. }

2.JpaSpecificationExecutor提供了以下接口


   
   
  
  
          
  1. public interface JpaSpecificationExecutor<T> {
  2. T findOne(Specification<T> spec);
  3. List<T> findAll(Specification<T> spec);
  4. Page<T> findAll(Specification<T> spec, Pageable pageable);
  5. List<T> findAll(Specification<T> spec, Sort sort);
  6. long count(Specification<T> spec);
  7. }
  8. //其中Specification就是需要我們傳入查詢方法的參數,它是一個接口
  9. public interface Specification<T> {
  10. Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
  11. }

提供唯一的一個方法toPredicate,我們只要按照JPA 2.0 criteria api寫好查詢條件就可以了,關於JPA 2.0 criteria api的介紹和使用,歡迎參考 
http://blog.csdn.net/dracotianlong/article/details/28445725 

http://developer.51cto.com/art/200911/162722.htm

3.接下來我們在service bean


   
   
  
  
          
  1. @Service
  2. public class TaskService {
  3. @Autowired TaskDao taskDao ;
  4. /**
  5. * 復雜查詢測試
  6. * @param page
  7. * @param size
  8. * @return
  9. */
  10. public Page<Task> findBySepc(int page, int size){
  11. PageRequest pageReq = this.buildPageRequest(page, size);
  12. Page<Task> tasks = this.taskDao.findAll( new MySpec(), pageReq);
  13.          //傳入了new MySpec() 既下面定義的匿名內部類 其中定義了查詢條件
  14. return tasks;
  15. }
  16. /**
  17. * 建立分頁排序請求
  18. * @param page
  19. * @param size
  20. * @return
  21. */
  22. private PageRequest buildPageRequest(int page, int size) {
  23. Sort sort = new Sort(Direction.DESC, "createTime");
  24. return new PageRequest(page,size, sort);
  25. }
  26. /**
  27. * 建立查詢條件
  28. */
  29. private class MySpec implements Specification<Task>{
  30. @Override
  31. public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
  32. //1.混合條件查詢
  33. Path<String> exp1 = root.get( "taskName");
  34. Path<Date> exp2 = root.get( "createTime");
  35. Path<String> exp3 = root.get( "taskDetail");
  36. Predicate predicate = cb.and(cb.like(exp1, "%taskName%"),cb.lessThan(exp2, new Date()));
  37. return cb.or(predicate,cb.equal(exp3, "kkk"));
  38. /* 類似的sql語句為:
  39. Hibernate:
  40. select
  41. count(task0_.id) as col_0_0_
  42. from
  43. tb_task task0_
  44. where
  45. (
  46. task0_.task_name like ?
  47. )
  48. and task0_.create_time<?
  49. or task0_.task_detail=?
  50. */
  51. //2.多表查詢
  52. Join<Task,Project> join = root.join( "project", JoinType.INNER);
  53. Path<String> exp4 = join.get( "projectName");
  54. return cb.like(exp4, "%projectName%");
  55. /* Hibernate:
  56. select
  57. count(task0_.id) as col_0_0_
  58. from
  59. tb_task task0_
  60. inner join
  61. tb_project project1_
  62. on task0_.project_id=project1_.id
  63. where
  64. project1_.project_name like ?*/
  65. return null ;
  66. }
  67. }
  68. }

4.實體類task代碼如下


   
   
  
  
          
  1. @Entity
  2. @Table(name = "tb_task")
  3. public class Task {
  4. private Long id ;
  5. private String taskName ;
  6. private Date createTime ;
  7. private Project project;
  8. private String taskDetail ;
  9. @Id
  10. @GeneratedValue(strategy = GenerationType.IDENTITY)
  11. public Long getId() {
  12. return id;
  13. }
  14. public void setId(Long id) {
  15. this.id = id;
  16. }
  17. @Column(name = "task_name")
  18. public String getTaskName() {
  19. return taskName;
  20. }
  21. public void setTaskName(String taskName) {
  22. this.taskName = taskName;
  23. }
  24. @Column(name = "create_time")
  25. @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
  26. public Date getCreateTime() {
  27. return createTime;
  28. }
  29. public void setCreateTime(Date createTime) {
  30. this.createTime = createTime;
  31. }
  32. @Column(name = "task_detail")
  33. public String getTaskDetail() {
  34. return taskDetail;
  35. }
  36. public void setTaskDetail(String taskDetail) {
  37. this.taskDetail = taskDetail;
  38. }
  39. @ManyToOne(fetch = FetchType.LAZY)
  40. @JoinColumn(name = "project_id")
  41. public Project getProject() {
  42. return project;
  43. }
  44. public void setProject(Project project) {
  45. this.project = project;
  46. }
  47. }

通過重寫toPredicate方法,返回一個查詢 Predicate,spring data jpa會幫我們進行查詢。
 

也許你覺得,每次都要寫一個類來實現Specification很麻煩,那或許你可以這么寫


   
   
  
  
          
  1. public class TaskSpec {
  2. public static Specification<Task> method1(){
  3. return new Specification<Task>(){
  4. @Override
  5. public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
  6. return null;
  7. }
  8. };
  9. }
  10. public static Specification<Task> method2(){
  11. return new Specification<Task>(){
  12. @Override
  13. public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
  14. return null;
  15. }
  16. };
  17. }
  18. }

那么用的時候,我們就這么用

Page<Task> tasks = this.taskDao.findAll(TaskSpec.method1(), pageReq);

  
  
 
 
         

 

五.Spring data jpa + QueryDSL 進行復雜查詢

  1. QueryDSL僅僅是一個通用的查詢框架,專注於通過Java API構建類型安全的SQL查詢。
  2. Querydsl可以通過一組通用的查詢API為用戶構建出適合不同類型ORM框架或者是SQL的查詢語句,也就是說QueryDSL是基於各種ORM框架以及SQL之上的一個通用的查詢框架。
  3. 借助QueryDSL可以在任何支持的ORM框架或者SQL平台上以一種通用的API方式來構建查詢。目前QueryDSL支持的平台包括JPA,JDO,SQL,Java Collections,RDF,Lucene,Hibernate Search。

P.s.配置可以根據官網介紹來配置

1 實體類

城市類:


   
   
  
  
          
  1. @Entity
  2. @Table(name = "t_city", schema = "test", catalog = "")
  3. public class TCity {
  4. //省略JPA注解標識
  5. private int id;
  6. private String name;
  7. private String state;
  8. private String country;
  9. private String map;
  10. }

 

旅館類:


   
   
  
  
          
  1. @Entity
  2. @Table(name = "t_hotel", schema = "test", catalog = "")
  3. public class THotel {
  4. //省略JPA注解標識
  5. private int id;
  6. private String name;
  7. private String address;
  8. private Integer city; //保存着城市的id主鍵
  9. }

2 單表動態分頁查詢

Spring Data JPA中提供了QueryDslPredicateExecutor接口,用於支持QueryDSL的查詢操作


   
   
  
  
          
  1. public interface tCityRepository extends JpaRepository<TCity, Integer>, QueryDslPredicateExecutor<TCity> {
  2. }

這樣的話單表動態查詢就可以參考如下代碼:


   
   
  
  
          
  1. //查找出Id小於3,並且名稱帶有`shanghai`的記錄.
  2. //動態條件
  3. QTCity qtCity = QTCity.tCity; //SDL實體類
  4. //該Predicate為querydsl下的類,支持嵌套組裝復雜查詢條件
  5. Predicate predicate = qtCity.id.longValue().lt( 3).and(qtCity.name.like( "shanghai"));
  6. //分頁排序
  7. Sort sort = new Sort( new Sort.Order(Sort.Direction.ASC, "id"));
  8. PageRequest pageRequest = new PageRequest( 0, 10,sort);
  9. //查找結果
  10. Page<TCity> tCityPage = tCityRepository.findAll(predicate,pageRequest);

3 多表動態查詢

QueryDSL對多表查詢提供了一個很好地封裝,看下面代碼:


   
   
  
  
          
  1. /**
  2. * 關聯查詢示例,查詢出城市和對應的旅店
  3. * @param predicate 查詢條件
  4. * @return 查詢實體
  5. */
  6. @Override
  7. public List<Tuple> findCityAndHotel(Predicate predicate) {
  8. JPAQueryFactory queryFactory = new JPAQueryFactory(em);
  9. JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity,QTHotel.tHotel)
  10. .from(QTCity.tCity)
  11. .leftJoin(QTHotel.tHotel)
  12. .on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));
  13. //添加查詢條件
  14. jpaQuery.where(predicate);
  15. //拿到結果
  16. return jpaQuery.fetch();
  17. }

城市表左連接旅店表,當該旅店屬於這個城市時查詢出兩者的詳細字段,存放到一個Tuple的多元組中.相比原生sql,簡單清晰了很多.
那么該怎么調用這個方法呢?


   
   
  
  
          
  1. @Test
  2. public void findByLeftJoin(){
  3. QTCity qtCity = QTCity.tCity;
  4. QTHotel qtHotel = QTHotel.tHotel;
  5. //查詢條件
  6. Predicate predicate = qtCity.name.like( "shanghai");
  7. //調用
  8. List<Tuple> result = tCityRepository.findCityAndHotel(predicate);
  9. //對多元組取出數據,這個和select時的數據相匹配
  10. for (Tuple row : result) {
  11. System.out.println( "qtCity:"+row.get(qtCity));
  12. System.out.println( "qtHotel:"+row.get(qtHotel));
  13. System.out.println( "--------------------");
  14. }
  15. System.out.println(result);
  16. }

 

這樣做的話避免了返回Object[]數組,下面是自動生成的sql語句:


   
   
  
  
          
  1. select
  2. tcity0_.id as id1_0_0_,
  3. thotel1_.id as id1_1_1_,
  4. tcity0_.country as country2_0_0_,
  5. tcity0_.map as map3_0_0_,
  6. tcity0_.name as name4_0_0_,
  7. tcity0_.state as state5_0_0_,
  8. thotel1_.address as address2_1_1_,
  9. thotel1_.city as city3_1_1_,
  10. thotel1_.name as name4_1_1_
  11. from
  12. t_city tcity0_
  13. left outer join
  14. t_hotel thotel1_
  15. on (
  16. cast(thotel1_.city as signed)= cast(tcity0_.id as signed)
  17. )
  18. where
  19. tcity0_.name like ? escape '!'

4 多表動態分頁查詢

分頁查詢對於queryDSL無論什么樣的sql只需要寫一遍,會自動轉換為相應的count查詢,也就避免了文章開始的問題4,下面代碼是對上面的查詢加上分頁功能:


   
   
  
  
          
  1. @Override
  2. public QueryResults<Tuple> findCityAndHotelPage(Predicate predicate,Pageable pageable) {
  3. JPAQueryFactory queryFactory = new JPAQueryFactory(em);
  4. JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity.id,QTHotel.tHotel)
  5. .from(QTCity.tCity)
  6. .leftJoin(QTHotel.tHotel)
  7. .on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()))
  8. .where(predicate)
  9. .offset(pageable.getOffset())
  10. .limit(pageable.getPageSize());
  11. //拿到分頁結果
  12. return jpaQuery.fetchResults();
  13. }

和上面不同之處在於這里使用了offsetlimit限制查詢結果.並且返回一個QueryResults,該類會自動實現count查詢和結果查詢,並進行封裝.
調用形式如下:


   
   
  
  
          
  1. @Test
  2. public void findByLeftJoinPage(){
  3. QTCity qtCity = QTCity.tCity;
  4. QTHotel qtHotel = QTHotel.tHotel;
  5. //條件
  6. Predicate predicate = qtCity.name.like( "shanghai");
  7. //分頁
  8. PageRequest pageRequest = new PageRequest( 0, 10);
  9. //調用查詢
  10. QueryResults<Tuple> result = tCityRepository.findCityAndHotelPage(predicate,pageRequest);
  11. //結果取出
  12. for (Tuple row : result.getResults()) {
  13. System.out.println( "qtCity:"+row.get(qtCity));
  14. System.out.println( "qtHotel:"+row.get(qtHotel));
  15. System.out.println( "--------------------");
  16. }
  17. //取出count查詢總數
  18. System.out.println(result.getTotal());
  19. }

生成的原生count查詢sql,當該count查詢結果為0的話,則直接返回,並不會再進行具體數據查詢:


   
   
  
  
          
  1. select
  2. count(tcity0_.id) as col_0_0_
  3. from
  4. t_city tcity0_
  5. left outer join
  6. t_hotel thotel1_
  7. on (
  8. cast(thotel1_.city as signed)= cast(tcity0_.id as signed)
  9. )
  10. where
  11. tcity0_.name like ? escape '!'

生成的原生查詢sql:


   
   
  
  
          
  1. select
  2. tcity0_.id as id1_0_0_,
  3. thotel1_.id as id1_1_1_,
  4. tcity0_.country as country2_0_0_,
  5. tcity0_.map as map3_0_0_,
  6. tcity0_.name as name4_0_0_,
  7. tcity0_.state as state5_0_0_,
  8. thotel1_.address as address2_1_1_,
  9. thotel1_.city as city3_1_1_,
  10. thotel1_.name as name4_1_1_
  11. from
  12. t_city tcity0_
  13. left outer join
  14. t_hotel thotel1_
  15. on (
  16. cast(thotel1_.city as signed)= cast(tcity0_.id as signed)
  17. )
  18. where
  19. tcity0_.name like ? escape '!' limit ?

查看打印,可以發現對應的city也都是同一個對象,hotel是不同的對象.

5 改造

有了上面的經驗,改造就變得相當容易了.
首先前面的一堆sql可以寫成如下形式,無非是多了一些select和left join


   
   
  
  
          
  1. JPAQueryFactory factory = new JPAQueryFactory(entityManager);
  2. factory.select($.pcardCardOrder)
  3. .select($.pcardVcardMake.vcardMakeDes)
  4. .select($.pcardVtype.cardnumRuleId,$.pcardVtype.vtypeNm)
  5. .select($.pcardCardbin)
  6. .leftJoin($.pcardVcardMake).on($.pcardCardOrder.makeId.eq($.pcardVcardMake.vcardMakeId))
  7. //......省略

查詢條件使用Predicate代替,放在service拼接,或者寫一個生產條件的工廠都可以.

 jpaQuery.where(predicate);
  
  
 
 
         

最后的分頁處理就和之前的一樣了


   
   
  
  
          
  1. jpaQuery.offset(pageable.getOffset())
  2. .limit(pageable.getPageSize());
  3. return jpaQuery.fetchResults();

 

寫在最后:

    個人認為jpa的意義就在於少用原生sql 為了方便開發 封裝已經是在所難免了. 推薦多使用簡單查詢,需要使用動態查詢的時候推薦使用JpaSpecificationExecutor個人認為比較好用.

    雖然我還是喜歡原生的寫法...

另外很多時候簡單的條件可以在server層進行判斷調用不同的Dao層方法就可以。

 

P.s.參考資料 

 

  1. 使用QueryDSL
  2. Spring Data JPA 實例查詢
  3. Spring Data JPA - Reference Documentation
  4. Querydsl Reference Guide

 

 

 

 

 

 

 

 

 


免責聲明!

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



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