Spring Data JPA使用必備(三):Spring Data JPA自定義SQL寫法

Spring Data JPA的前兩篇已經(jīng)寫了通過方法名格式自動生成SQL,也簡單的提到了@Query注解浊服。但是往往真正的業(yè)務(wù)邏輯里面攘轩,這些是完全不夠用的,涉及到一些稍微復(fù)雜一點的查詢就會有點問題阎曹,如根據(jù)一組條件中的某幾個條件查詢(條件不固定),然后再加上分頁煞檩、排序处嫌,這個時候只是使用之前的方法就有點捉襟見肘啦。

這篇博客的篇幅不會很長斟湃,主要是講兩個點熏迹,一個是在Spring Data JPA系列的第一篇博客中提到的@Query注解,一個就是通過Specification組合動態(tài)條件以及PageableSort實現(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類里面沒有這個方法,那是因為在JpaRepositoryJpaSpecificationExecutor里面有很多內(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)載請注明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市懈词,隨后出現(xiàn)的幾起案子蛇耀,更是在濱河造成了極大的恐慌,老刑警劉巖坎弯,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纺涤,死亡現(xiàn)場離奇詭異,居然都是意外死亡抠忘,警方通過查閱死者的電腦和手機撩炊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崎脉,“玉大人拧咳,你說我怎么就攤上這事∏糇疲” “怎么了骆膝?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵祭衩,是天一觀的道長。 經(jīng)常有香客問我阅签,道長掐暮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任愉择,我火速辦了婚禮劫乱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锥涕。我一直安慰自己,他們只是感情好狭吼,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布层坠。 她就那樣靜靜地躺著,像睡著了一般刁笙。 火紅的嫁衣襯著肌膚如雪破花。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天疲吸,我揣著相機與錄音座每,去河邊找鬼。 笑死摘悴,一個胖子當(dāng)著我的面吹牛峭梳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹂喻,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼葱椭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了口四?” 一聲冷哼從身側(cè)響起孵运,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔓彩,沒想到半個月后治笨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡赤嚼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年旷赖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片探膊。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡杠愧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逞壁,到底是詐尸還是另有隱情流济,我是刑警寧澤锐锣,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站绳瘟,受9級特大地震影響雕憔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糖声,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一斤彼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蘸泻,春花似錦琉苇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抡诞,卻和暖如春穷蛹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昼汗。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工肴熏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人顷窒。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓蛙吏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蹋肮。 傳聞我的和親對象是個殘疾皇子出刷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容