Spring Data JPA的前兩篇已經(jīng)寫了通過方法名格式自動生成SQL,也簡單的提到了@Query
注解浊服。但是往往真正的業(yè)務(wù)邏輯里面攘轩,這些是完全不夠用的,涉及到一些稍微復(fù)雜一點的查詢就會有點問題阎曹,如根據(jù)一組條件中的某幾個條件查詢(條件不固定),然后再加上分頁煞檩、排序处嫌,這個時候只是使用之前的方法就有點捉襟見肘啦。
這篇博客的篇幅不會很長斟湃,主要是講兩個點熏迹,一個是在Spring Data JPA系列的第一篇博客中提到的@Query
注解,一個就是通過Specification
組合動態(tài)條件以及Pageable
和Sort
實現(xiàn)分頁和排序凝赛。
@Query
注解
@Query
注解使用起來很簡單注暗,默認(rèn)的屬性是value
,就是當(dāng)前寫的SQL語句墓猎,有時會用到nativeQuery
屬性捆昏,這個屬性是用來標(biāo)記當(dāng)前的SQL是本地SQL,還是符合JPA語法規(guī)范的SQL毙沾。這里需要解釋一下本地SQL和JPA語法規(guī)范的SQL區(qū)別骗卜。
本地SQL,是根據(jù)實際使用的數(shù)據(jù)庫類型寫的SQL搀军,這種SQL中使用到的一些語法格式不能被JPA解析以及可能不兼容其他數(shù)據(jù)庫膨俐,這種SQL稱為本地SQL,此時需要將
nativeQuery
屬性設(shè)置為true
罩句,否則會報錯。JPA語法規(guī)范的SQL敛摘,往往這種SQL本身是不適用于任何數(shù)據(jù)庫的门烂,需要JPA將這種SQL轉(zhuǎn)換成真正當(dāng)前數(shù)據(jù)庫所需要的SQL語法格式。
注意:JPA很好的一個特性就是用JPA語法規(guī)范寫的SQL,會根據(jù)當(dāng)前系統(tǒng)使用的數(shù)據(jù)庫類型改變生成的SQL語法屯远,兼容數(shù)據(jù)庫類型的切換蔓姚,如之前使用的是MySQL,現(xiàn)在換成Oracle慨丐,由于不同類型的數(shù)據(jù)庫坡脐,SQL語法會有區(qū)別,如果使用的是mybatis房揭,就需要手動去改SQL兼容Oracle备闲,而JPA就不用啦,無縫對接捅暴。
說明:很大的時候使用JPA感覺都是為了兼容后期可能會有數(shù)據(jù)庫切換的問題恬砂,所以在使用JPA的時候,不要去使用本地SQL蓬痒,這就違背了使用JPA的初衷泻骤,讓nativeQuery屬性保持默認(rèn)值就可以啦!(切記切記)
舉個栗子
根據(jù)這個栗子再引出一些常用的東西梧奢,代碼如下:
//示例1
@Query("select t from Device t where t.deviceSn=:deviceSn and t.deleteFlag=1")
Device findExistDevice(@Param("deviceSn") String deviceSn);
//示例2
@Query("select t from Device t where t.deviceSn=:deviceSn and t.deviceType =:deviceType and t.deleteFlag=1")
Device findExistDevice(@Param("deviceSn") String deviceSn,@Param("deviceType")Integer deviceType);
//示例3
@Query("select t from Device t where t.deviceSn=?1 and t.deviceType = ?2 and t.deleteFlag=1")
Device findDevice(String deviceSn,Integer deviceType);
- 在SQL上使用占位符的兩種方式狱掂,第一種是使用":"后加變量的名稱,第二種是使用"?"后加方法參數(shù)的位置亲轨。如果使用":"的話趋惨,需要使用
@Param
注解來指定變量名;如果使用"?"就需要注意參數(shù)的位置瓶埋。 - SQL語句中直接用實體類代表表名希柿,因為在實體類中使用了
@Table
注解,將該實體類和表進行了關(guān)聯(lián)养筒。
還有其他有使用SpEL表達式等等就不多說了曾撤,基本沒什么用,也是一些不常用的東西(可以了解了解去裝X)晕粪。
@Modifying注解
相信在正常的項目開發(fā)中都會涉及到修改數(shù)據(jù)信息的操作挤悉,如邏輯刪除、封號巫湘、解封装悲、修改用戶名、頭像等等尚氛。在使用JPA的時候诀诊,如果@Query
涉及到update
就必須同時加上@Modifying
注解,注明當(dāng)前方法是修改操作阅嘶。
如下代碼:
@Modifying
@Query("update Device t set t.userName =:userName where t.id =:userId")
User updateUserName(@Param("userId") Long userId,@Param("userName") String userName);
到這里就寫完了@Query
的基本用法属瓣,很多復(fù)雜的用法就不多說了载迄,用不上。
Specification+Pageable+Sort組合復(fù)雜SQL
在查詢列表數(shù)據(jù)的時候抡蛙,這三個基本都是可以用上的护昧,如果不涉及到排序、分頁粗截,只是組合動態(tài)的查詢條件惋耙,Specification
就夠用了,下面來依次說一下熊昌。
Specification動態(tài)組合查詢
組合條件查詢很常見绽榛,使用@Query
或者根據(jù)方法名自動生成SQL實現(xiàn)都不是很方便,JPA提供了Specification
解決了這個問題浴捆。如下代碼:
package com.itcrud.jpa;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import java.util.List;
/**
* @Author: IT-CRUD
*/
public class ArticleService {
@Autowired
private ArticleRepository repository;
public void articleList(final ArticleReqDTO reqDTO) {
Specification<Article> spec = (root, query, builder) -> {
List<Predicate> predicates = Lists.newArrayList();
//等于蒜田,根據(jù)ID精確查詢
if (reqDTO.getId() != null) {
predicates.add(builder.equal(root.get("id"), reqDTO.getId()));
}
//模糊,關(guān)鍵字匹配文章摘要+標(biāo)題
if (StringUtils.isNotBlank(reqDTO.getKeywords())) {
predicates.add(builder.or(
builder.like(root.get("title"), "%" + reqDTO.getKeywords() + "%"),
builder.like(root.get("abstract"), "%" + reqDTO.getKeywords() + "%")
));
}
//in范圍选泻,根據(jù)文章分類查詢
if (CollectionUtils.isNotEmpty(reqDTO.getCategories())) {
CriteriaBuilder.In<Object> builderIn = builder.in(root.get("category"));
for (Integer category : reqDTO.getCategories()) {
builderIn = builderIn.value(category);
}
predicates.add(builderIn);
}
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
};
List<Article> articles = repository.findAll(spec);
}
}
這里具體的Article
實體類冲粤、ArticleReqDTO
實體類就不展示出來了,太占地方页眯,意會即可梯捕。但是ArticleRepository
還是要看一下,里面不僅要繼承JpaRepository
窝撵,還要繼承另一個接口JpaSpecificationExecutor
傀顾。看代碼:
//泛型的Article是代表數(shù)據(jù)庫的映射的類碌奉,Long代表主鍵的類型
public interface ArticleRepository extends JpaRepository<Article, Long>, JpaSpecificationExecutor<Article> {
}
上面在ArticleService
類中使用到了findAll
方法短曾,但是在ArticleRepository
類里面沒有這個方法,那是因為在JpaRepository
和JpaSpecificationExecutor
里面有很多內(nèi)置的方法赐劣,這里使用的是Specification
嫉拐,很容易可以理解到使用的都是JpaSpecificationExecutor
內(nèi)的方法,這個接口里面的方法都很簡單魁兼。(這些內(nèi)置的方法也挺好用婉徘,方法很多)
組合條件的代碼不是很難,就是從寫SQL轉(zhuǎn)換成用代碼寫有一個適應(yīng)的過程咐汞,另外用SQL的話會更直觀一點盖呼,這個代碼寫了以后需要在日志里面打印出SQL語句,檢查SQL的正確性化撕。代碼中builder
內(nèi)還有很多可用的API几晤,比如大于、小于植阴、等于等操作锌仅,就不一一的列舉啦章钾。
Pageable+Sort分頁排序
分頁排序往往都是一起用的墙贱,Pageable
的實現(xiàn)類PageResult
本身的構(gòu)造方法里面就支持自動構(gòu)建Sort
對象热芹。
看下面的代碼:
//省略部分……
public void articleList(final ArticleReqDTO reqDTO) {
Specification<Article> spec = (root, query, builder) -> {
List<Predicate> predicates = Lists.newArrayList();
//省略部分……
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
};
Pageable pageable = new PageRequest(reqDTO.getPageNo() - 1
, reqDTO.getPageSize(), Sort.Direction.ASC, "createTime");//按照createTime升序
Page<Article> pageInfo = repository.findAll(spec, pageable);
}
這里代碼的省略部分和上面介紹Specification
的代碼一樣,可以參考惨撇,這里就不寫出來啦伊脓。重點代碼是最后兩行。需要注意的就是pageNo
參數(shù)魁衙,JPA是從0開始报腔,如果外部傳入的頁碼是從1開始,就需要做減1的操作剖淀;另外就是這里看似沒有涉及到Sort
纯蛾,但是實際是用到的,看PageResult
構(gòu)造方法源碼如下:
public PageRequest(int page, int size, Direction direction, String... properties) {
//使用后兩個參數(shù)纵隔,構(gòu)建了Sort對象翻诉,排序支持多個字段
this(page, size, new Sort(direction, properties));
}
其他都是常規(guī)操作啦。具體查到的數(shù)據(jù)捌刮,已經(jīng)和分頁相關(guān)的參數(shù)都封裝在pageInfo
里面碰煌。
還有一種組合是Specification+Sort
對查詢的數(shù)據(jù)按照指定格式排序,Sort
的創(chuàng)建可以參考一下上面的代碼绅作,也可以直接去看源碼芦圾,比較簡單,不贅述俄认。
總結(jié)
草草的寫了三篇關(guān)于JPA的博客个少,寫了基本的用法,從起初第一篇的Repository
接口的創(chuàng)建眯杏、實體類的注解說明夜焦,基本的操作,然后第二篇寫了JPA的特性之根據(jù)方法自動生成SQL役拴,給出了一些常用的模板糊探。最后就是這一篇,涉及到手動寫SQL和一些單表的復(fù)雜SQL編寫河闰】破剑基本的使用已經(jīng)完全沒有問題了,但是這些都是單表操作姜性,如果涉及多表聯(lián)查瞪慧,就會不太好使,為了暫時的方便部念,在項目開發(fā)的過程中都是直接寫native SQL弃酌,放在@Query
注解里面氨菇。雖然個人是極其不推薦的,可是真是JPA對連表查詢不友好妓湘,操作也比較麻煩查蓉。
以后如果很閑的話,可能會更新寫JPA連表查的相關(guān)博客榜贴。之所以不寫有下面幾個原因:
- 如果只是簡單表聯(lián)查豌研,完全可以拆成多次來查,有時候多次查比連表查更方便快捷唬党,效率上往往單表操作比聯(lián)表查更高
- 如果涉及到非常復(fù)雜的連表查詢鹃共,即使你會用JPA連表查的操作,你也不會去做驶拱,涉及的代碼量很大霜浴,而且不方便,這個時候不如寫native SQL來的快蓝纲,效率高
- 雖然不推薦寫native SQL阴孟,但是項目中需要使用到很復(fù)雜SQL查詢的地方一般都是很少的,對這些復(fù)雜查詢使用了復(fù)雜SQL驻龟,以后切換數(shù)據(jù)庫改動量也不大
總結(jié):單表操作直接懟温眉,簡單聯(lián)查拆開搞,復(fù)雜SQL不用怕翁狐,本地SQL來補充类溢,代碼優(yōu)雅最大化,切庫改動也不大露懒。
本文作者:IT-CRUD
原文地址:http://blog.itcrud.com/blogs/2019/05/spring-jpa-base-customize-sql-original
版權(quán)歸作者所有闯冷,轉(zhuǎn)載請注明出處