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

一.Spring data jpa 簡介
首先我並不推薦使用jpa做爲ORM框架,畢竟對於負責查詢的時候仍是不太靈活,仍是建議使用mybatis,本身寫sql比較好.可是若是公司用這個就沒辦法了,能夠學習一下,對於簡單查詢仍是很是好用的.html

    首先JPA是Java持久層API,由Sun公司開發, 但願整合ORM技術,實現天下歸一.  誕生的原因是爲了整合第三方ORM框架,創建一種標準的方式,目前也是在按照這個方向發展,可是還沒能徹底實現。在ORM框架中,Hibernate是一支很大的部隊,使用很普遍,也很方便,能力也很強,同時Hibernate也是和JPA整合的比較良好,咱們能夠認爲JPA是標準,事實上也是,JPA幾乎都是接口,實現都是Hibernate在作,宏觀上面看,在JPA的統一之下Hibernate很良好的運行。git

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

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

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

    咱們都知道,在使用持久化工具的時候,通常都有一個對象來操做數據庫,在原生的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的方式spring

     返回值爲對象的意義sql

    是jpa查詢表內容返回值基本上都是對象,可是僅僅須要一個字段返回總體對象不是會有不少數據冗餘嗎,其實大多數狀況對一個數據表的查詢不可能只有一次或者說這個表不只僅是這一次會用到,若是我寫好一個返回對象的方法,以後均可以直接調用,通常狀況下多出一點數據對網絡的壓力能夠忽略不計,而這樣對開發效率的提高仍是很大的.若是僅僅想獲得一部分字段也能夠新建一個只有想要字段的Entity.mongodb

 

二.Spring data jpa 基本使用
對於配置方法和基礎的dao層寫法等不作介紹,基礎篇僅當作一個方法字典.數據庫

    1.核心方法api

查詢全部數據 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

     例如:

User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
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_排序
Sort sort =new Sort(Sort.Direction.ASC,"id");
//其中第一個參數表示是降序仍是升序(此處表示升序)
//第二個參數表示你要按你的 entity(記住是entity中聲明的變量,不是數據庫中表對應的字段)中的那個變量進行排序
PageRequest_分頁
PageRequest pageRequest = new PageRequest(index, num, sort);
//index偏移量 num查詢數量 sort排序
    分頁+排序實現:

DemoBean demoBean = new DemoBean();
demoBean.setAppId(appId); //查詢條件
//建立查詢參數
Example<DemoBean> example = Example.of(demoBean);
//獲取排序對象
Sort sort = new Sort(Sort.Direction.DESC, "id");
//建立分頁對象
PageRequest pageRequest = new PageRequest(index, num, sort);
//分頁查詢
return demoRepository.findAll(example, pageRequest).getContent();
Example_實例查詢
    建立一個ExampleMatcher對象,最後再用Example的of方法構造相應的Example對象並傳遞給相關查詢方法。咱們看看Spring的例子。

Person person = new Person();
person.setFirstname("Dave"); //Firstname = 'Dave'

ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", GenericPropertyMatchers.startsWith()) //姓名採用「開始匹配」的方式查詢
.withIgnorePaths("int"); //忽略屬性:是否關注。由於是基本類型,須要忽略掉

Example<Person> example = Example.of(person, matcher); //Example根據域對象和配置建立一個新的ExampleMatcher
    ExampleMatcher用於建立一個查詢對象,上面的代碼就建立了一個查詢對象。withIgnorePaths方法用來排除某個屬性的查詢。withIncludeNullValues方法讓空值也參與查詢,就是咱們設置了對象的姓,而名爲空值.

 

一、概念定義:

    上面例子中,是這樣建立「實例」的: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對象,經過一個現有的客戶對象做爲例子,查詢和這個例子相匹配的對象。

 

二、特色及約束(侷限性):

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

    B、不支持過濾條件分組。即不支持過濾條件用 or(或) 來鏈接,全部的過濾查件,都是簡單一層的用 and(而且) 鏈接。

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

 

三、ExampleMatcher的使用 :

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

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

一些方法:
一、關於基本數據類型。
實體對象中,避免使用基本數據類型,採用包裝器類型。若是已經採用了基本類型,

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

二、Null值處理方式。

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

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

默認建立匹配器時,字符串採用的是精確匹配、不忽略大小寫,能夠經過操做方法改變這種默認匹配,以知足大多數查詢條件的須要,如將「字符串匹配方式」改成CONTAINING(包含,模糊匹配),這是比較經常使用的狀況。對於個別屬性須要特定的查詢方式,能夠經過配置「屬性特定查詢方式」來知足要求。

四、非字符串屬性

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

五、忽略大小寫的問題。

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

一些例子:
綜合使用:

//建立查詢條件數據對象
Customer customer = new Customer();
customer.setName("zhang");
customer.setAddress("河南省");
customer.setRemark("BB");

//建立匹配器,即如何使用查詢條件
ExampleMatcher matcher = ExampleMatcher.matching() //構建對象
.withStringMatcher(StringMatcher.CONTAINING) //改變默認字符串匹配方式:模糊查詢
.withIgnoreCase(true) //改變默認大小寫忽略方式:忽略大小寫
.withMatcher("address", GenericPropertyMatchers.startsWith()) //地址採用「開始匹配」的方式查詢
.withIgnorePaths("focus"); //忽略屬性:是否關注。由於是基本類型,須要忽略掉

//建立實例
Example<Customer> ex = Example.of(customer, matcher);

//查詢
List<Customer> ls = dao.findAll(ex);
查詢null值:

     //建立查詢條件數據對象
Customer customer = new Customer();

//建立匹配器,即如何使用查詢條件
ExampleMatcher matcher = ExampleMatcher.matching() //構建對象
.withIncludeNullValues() //改變「Null值處理方式」:包括
.withIgnorePaths("id","name","sex","age","focus","addTime","remark","customerType"); //忽略其餘屬性

//建立實例
Example<Customer> ex = Example.of(customer, matcher);

//查詢
List<Customer> ls = dao.findAll(ex);
 

三.Spring data jpa 註解
1.Repository註解

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

@Query // 自定義Sql

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

@Async //異步操做

 

2.Entity註解

@Entity //不寫@Table默認爲user
@Table(name="t_user") //自定義表名
public class user {

@Id //主鍵
@GeneratedValue(strategy = GenerationType.AUTO)//採用數據庫自增方式生成主鍵
//JPA提供的四種標準用法爲TABLE,SEQUENCE,IDENTITY,AUTO.
//TABLE:使用一個特定的數據庫表格來保存主鍵。
//SEQUENCE:根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列。
//IDENTITY:主鍵由數據庫自動生成(主要是自動增加型)
//AUTO:主鍵由程序控制。

@Transient //此字段不與數據庫關聯
@Version//此字段加上樂觀鎖
//字段爲name,不容許爲空,用戶名惟一
@Column(name = "name", unique = true, nullable = false)
private String name;

@Temporal(TemporalType.DATE)//生成yyyy-MM-dd類型的日期
//出參時間格式化
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
//入參時,請求報文只須要傳入yyyymmddhhmmss字符串進來,則自動轉換爲Date類型數據
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
private Date createTime;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
 

四.繼承JpaSpecificationExecutor接口進行復雜查詢
spring data jpa 經過建立方法名來作查詢,只能作簡單的查詢,那若是咱們要作複雜一些的查詢呢,多條件分頁怎麼辦,這裏,spring data jpa爲咱們提供了JpaSpecificationExecutor接口,只要簡單實現toPredicate方法就能夠實現複雜的查詢

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

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

public interface TaskDao extends JpaSpecificationExecutor<Task>{
}
2.JpaSpecificationExecutor提供瞭如下接口

public interface JpaSpecificationExecutor<T> {

T findOne(Specification<T> spec);

List<T> findAll(Specification<T> spec);

Page<T> findAll(Specification<T> spec, Pageable pageable);

List<T> findAll(Specification<T> spec, Sort sort);

long count(Specification<T> spec);
}

//其中Specification就是須要咱們傳入查詢方法的參數,它是一個接口


public interface Specification<T> {

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
提供惟一的一個方法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

@Service
public class TaskService {

@Autowired TaskDao taskDao ;


/**
* 複雜查詢測試
* @param page
* @param size
* @return
*/
public Page<Task> findBySepc(int page, int size){

PageRequest pageReq = this.buildPageRequest(page, size);
Page<Task> tasks = this.taskDao.findAll(new MySpec(), pageReq);
        //傳入了new MySpec() 既下面定義的匿名內部類 其中定義了查詢條件
return tasks;

}

/**
* 創建分頁排序請求
* @param page
* @param size
* @return
*/
private PageRequest buildPageRequest(int page, int size) {
Sort sort = new Sort(Direction.DESC,"createTime");
return new PageRequest(page,size, sort);
}

/**
* 創建查詢條件
*/
private class MySpec implements Specification<Task>{

@Override
public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

//1.混合條件查詢
Path<String> exp1 = root.get("taskName");
Path<Date> exp2 = root.get("createTime");
Path<String> exp3 = root.get("taskDetail");
Predicate predicate = cb.and(cb.like(exp1, "%taskName%"),cb.lessThan(exp2, new Date()));
return cb.or(predicate,cb.equal(exp3, "kkk"));

/* 相似的sql語句爲:
Hibernate:
select
count(task0_.id) as col_0_0_
from
tb_task task0_
where
(
task0_.task_name like ?
)
and task0_.create_time<?
or task0_.task_detail=?
*/

//2.多表查詢
Join<Task,Project> join = root.join("project", JoinType.INNER);
Path<String> exp4 = join.get("projectName");
return cb.like(exp4, "%projectName%");

/* Hibernate:
select
count(task0_.id) as col_0_0_
from
tb_task task0_
inner join
tb_project project1_
on task0_.project_id=project1_.id
where
project1_.project_name like ?*/
return null ;
}
}
}
4.實體類task代碼以下

@Entity
@Table(name = "tb_task")
public class Task {

private Long id ;
private String taskName ;
private Date createTime ;
private Project project;
private String taskDetail ;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}

@Column(name = "task_name")
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}

@Column(name = "create_time")
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}


@Column(name = "task_detail")
public String getTaskDetail() {
return taskDetail;
}
public void setTaskDetail(String taskDetail) {
this.taskDetail = taskDetail;
}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}

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

也許你以爲,每次都要寫一個類來實現Specification很麻煩,那或許你能夠這麼寫

public class TaskSpec {

public static Specification<Task> method1(){

return new Specification<Task>(){
@Override
public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return null;
}

};
}

public static Specification<Task> method2(){

return new Specification<Task>(){
@Override
public Predicate toPredicate(Root<Task> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return null;
}

};
}

}
那麼用的時候,咱們就這麼用

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

五.Spring data jpa + QueryDSL 進行復雜查詢
QueryDSL僅僅是一個通用的查詢框架,專一於經過Java API構建類型安全的SQL查詢。
Querydsl能夠經過一組通用的查詢API爲用戶構建出適合不一樣類型ORM框架或者是SQL的查詢語句,也就是說QueryDSL是基於各類ORM框架以及SQL之上的一個通用的查詢框架。
藉助QueryDSL能夠在任何支持的ORM框架或者SQL平臺上以一種通用的API方式來構建查詢。目前QueryDSL支持的平臺包括JPA,JDO,SQL,Java Collections,RDF,Lucene,Hibernate Search。
P.s.配置能夠根據官網介紹來配置

1 實體類

城市類:

@Entity
@Table(name = "t_city", schema = "test", catalog = "")
public class TCity {
//省略JPA註解標識
private int id;
private String name;
private String state;
private String country;
private String map;
}
 

旅館類:

@Entity
@Table(name = "t_hotel", schema = "test", catalog = "")
public class THotel {
//省略JPA註解標識
private int id;
private String name;
private String address;
private Integer city;//保存着城市的id主鍵
}
2 單表動態分頁查詢

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

public interface tCityRepository extends JpaRepository<TCity, Integer>, QueryDslPredicateExecutor<TCity> {
}
這樣的話單表動態查詢就能夠參考以下代碼:

//查找出Id小於3,而且名稱帶有`shanghai`的記錄.

//動態條件
QTCity qtCity = QTCity.tCity; //SDL實體類
//該Predicate爲querydsl下的類,支持嵌套組裝複雜查詢條件
Predicate predicate = qtCity.id.longValue().lt(3).and(qtCity.name.like("shanghai"));
//分頁排序
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,"id"));
PageRequest pageRequest = new PageRequest(0,10,sort);
//查找結果
Page<TCity> tCityPage = tCityRepository.findAll(predicate,pageRequest);
3 多表動態查詢

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

/**
* 關聯查詢示例,查詢出城市和對應的旅店
* @param predicate 查詢條件
* @return 查詢實體
*/
@Override
public List<Tuple> findCityAndHotel(Predicate predicate) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));
//添加查詢條件
jpaQuery.where(predicate);
//拿到結果
return jpaQuery.fetch();
}
城市表左鏈接旅店表,當該旅店屬於這個城市時查詢出二者的詳細字段,存放到一個Tuple的多元組中.相比原生sql,簡單清晰了不少.
那麼該怎麼調用這個方法呢?

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

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

select
tcity0_.id as id1_0_0_,
thotel1_.id as id1_1_1_,
tcity0_.country as country2_0_0_,
tcity0_.map as map3_0_0_,
tcity0_.name as name4_0_0_,
tcity0_.state as state5_0_0_,
thotel1_.address as address2_1_1_,
thotel1_.city as city3_1_1_,
thotel1_.name as name4_1_1_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape '!'
4 多表動態分頁查詢

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

@Override
public QueryResults<Tuple> findCityAndHotelPage(Predicate predicate,Pageable pageable) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity.id,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()))
.where(predicate)
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
//拿到分頁結果
return jpaQuery.fetchResults();
}
和上面不一樣之處在於這裏使用了offset和limit限制查詢結果.而且返回一個QueryResults,該類會自動實現count查詢和結果查詢,並進行封裝.
調用形式以下:

@Test
public void findByLeftJoinPage(){
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
//條件
Predicate predicate = qtCity.name.like("shanghai");
//分頁
PageRequest pageRequest = new PageRequest(0,10);
//調用查詢
QueryResults<Tuple> result = tCityRepository.findCityAndHotelPage(predicate,pageRequest);
//結果取出
for (Tuple row : result.getResults()) {
System.out.println("qtCity:"+row.get(qtCity));
System.out.println("qtHotel:"+row.get(qtHotel));
System.out.println("--------------------");
}
//取出count查詢總數
System.out.println(result.getTotal());
}
生成的原生count查詢sql,當該count查詢結果爲0的話,則直接返回,並不會再進行具體數據查詢:

select
count(tcity0_.id) as col_0_0_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape '!'
生成的原生查詢sql:

select
tcity0_.id as id1_0_0_,
thotel1_.id as id1_1_1_,
tcity0_.country as country2_0_0_,
tcity0_.map as map3_0_0_,
tcity0_.name as name4_0_0_,
tcity0_.state as state5_0_0_,
thotel1_.address as address2_1_1_,
thotel1_.city as city3_1_1_,
thotel1_.name as name4_1_1_
from
t_city tcity0_
left outer join
t_hotel thotel1_
on (
cast(thotel1_.city as signed)=cast(tcity0_.id as signed)
)
where
tcity0_.name like ? escape '!' limit ?
查看打印,能夠發現對應的city也都是同一個對象,hotel是不一樣的對象.

 

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

JPAQueryFactory factory = new JPAQueryFactory(entityManager);
factory.select($.pcardCardOrder)
.select($.pcardVcardMake.vcardMakeDes)
.select($.pcardVtype.cardnumRuleId,$.pcardVtype.vtypeNm)
.select($.pcardCardbin)
.leftJoin($.pcardVcardMake).on($.pcardCardOrder.makeId.eq($.pcardVcardMake.vcardMakeId))
//......省略
查詢條件使用Predicate代替,放在service拼接,或者寫一個生產條件的工廠均可以.

jpaQuery.where(predicate);
最後的分頁處理就和以前的同樣了

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

寫在最後:

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

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

另外不少時候簡單的條件能夠在server層進行判斷調用不一樣的Dao層方法就能夠。

 

P.s.參考資料 

 

使用QueryDSL
Spring Data JPA 實例查詢
Spring Data JPA - Reference Documentation
Querydsl Reference Guide
原文:https://blog.csdn.net/qq_30054997/article/details/79420141

參考:https://github.com/hope-for/GyJdbc

相關文章
相關標籤/搜索