Spring Data Jpa 讓@Query復(fù)雜查詢分頁支持實(shí)體返回

背景

??Spring Data Jpa 雖然可以減少代碼中Sql的數(shù)量嫌变,但其在復(fù)雜查詢中略顯乏力集索。網(wǎng)上很多文章都采用Java代碼的形式去實(shí)現(xiàn)復(fù)雜查詢,但這樣一來Sql的效率變得不可控羡棵。也有文章采用@Query 注解去執(zhí)行JPQL原生SQL,本人在使用過程中也傾向于這種方式她紫。
??但有時(shí)采用@Query方式,框架無法正常返回我們需要的類型屿讽。比如復(fù)雜查詢后后分頁昭灵,Repository方法的返回寫的是Page<User>吠裆,然而真正執(zhí)行后返回的類型卻變成了Page<Object[]>。究竟原因烂完,分析源碼后發(fā)現(xiàn)PagedExecution并未對(duì)執(zhí)行結(jié)果進(jìn)行處理试疙。

通過對(duì)Spring-Data-Jpa源碼的分析,發(fā)現(xiàn)在RepositoryFactorySupport.java中的getRepository方法(第124行)抠蚣,提供了鉤子方法使得我們可以在自己的RepositoryProxyPostProcessor中向目標(biāo)Repository注入MethodInterceptor(方法攔截器)祝旷。

    public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
        ...
        Object target = this.getTargetRepository(information);
        ProxyFactory result = new ProxyFactory();
        result.setTarget(target);
        result.setInterfaces(new Class[]{repositoryInterface, Repository.class});
        ...
        Iterator var8 = this.postProcessors.iterator();
        while(var8.hasNext()) {
            RepositoryProxyPostProcessor processor = (RepositoryProxyPostProcessor)var8.next();
            processor.postProcess(result, information);
        }
        ...
        result.addAdvice(new RepositoryFactorySupport.QueryExecutorMethodInterceptor(information, customImplementation, target));
        return result.getProxy(this.classLoader);
    }

本文將介紹通過自定義RepositoryProxyPostProcessor向Repository(我們應(yīng)用中聲明的Repository)中注入自定義MethodInterceptor以使得@Qurey復(fù)雜查詢能支持分頁P(yáng)OJO的返回。

1.編寫業(yè)務(wù)Repository嘶窄,并繼承JpaRepository

繼承JpaRepository使得返回結(jié)果支持分頁

@Repository
public interface UserRepository extends JpaRepository<User, String> {
    /**
     * 返回指定部門下邊的用戶
     *
     * @param officeId
     * @param pageable
     * @return
     */
    @Query(value = "SELECT user.id, user.name FROM User user, Office office WHERE user.officeId = office.id AND office.id = ?1 ")
    Page<User> getUserInOffice(String officeId, Pageable pageable);
}

上邊的查詢還是比較簡(jiǎn)單的怀跛,這只是為了作為例子好理解。如果需要你可以將Sql改得更加復(fù)雜柄冲。注意吻谋,我采用的是JPQL而非Native Sql。啟動(dòng)單元測(cè)試調(diào)用上述方法现横,結(jié)果的確返回了Page對(duì)象滨溉,但是當(dāng)你page.getContent()時(shí),會(huì)發(fā)現(xiàn)返回的結(jié)果為L(zhǎng)ist<Object[]>长赞。
接下來進(jìn)入主題

2.自定義MethodInterceptor晦攒,將content由List<Object[]>轉(zhuǎn)為L(zhǎng)ist<T>

繼承MethodInterceptor,重寫invoke方法執(zhí)行其他代理獲得Jpql返回結(jié)果集后得哆,將List<Object[]>轉(zhuǎn)為L(zhǎng)ist<T>脯颜。

public class JpqlBeanMethodInterceptor implements MethodInterceptor {

    /**
    * 用于存放QueryMethod對(duì)應(yīng)的字段和返回類型信息
    */
    private Map<Method, SelectAlias> selectAlias = new HashMap<>();

    public JpqlBeanMethodInterceptor(RepositoryInformation repositoryInformation) {
        Iterator<Method> iterable = repositoryInformation.getQueryMethods().iterator();
        SqlParser sqlParser = new DefaultSqlParser();
        while (iterable.hasNext()) {
            Method method = iterable.next();
            Query query = method.getAnnotation(Query.class);
            if (query == null || query.nativeQuery()) {
                continue;
            }
            //獲取返回類型
            Class clazz = getGenericReturnClass(method);
            if (clazz == null) {
                continue;
            }
            SelectAlias alias = sqlParser.getAlias(query.value(), clazz);
            if (alias == null) {
                continue;
            }
            selectAlias.put(method, alias);
        }
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //執(zhí)行方法獲得結(jié)果
        Object obj = invocation.proceed();
        if (!checkCanConvert(obj)) {
            return obj;
        }
        SelectAlias alias = selectAlias.get(invocation.getMethod());
        if (alias == null) {
            return obj;
        }
        List content = getPageContent((PageImpl) obj);
        convert(content, alias);
        return obj;
    }
    //由于篇幅原因只貼出部分代碼
    ...
}

上述代碼由于篇幅原因只貼出部分,后續(xù)整理完代碼后將共享出來贩据。代碼中sqlParser采用jsqlparser從Sql中取出返回的字段解析后轉(zhuǎn)換為SelectAlia栋操。注意這部分代碼是在構(gòu)造方法中執(zhí)行的,后續(xù)代碼決定了這部分代碼只會(huì)在Spring Boot啟動(dòng)的時(shí)候每個(gè)Repository執(zhí)行一次饱亮,以提高執(zhí)行效率矾芙。

3.自定義RepositoryProxyPostProcessor

在postProcess時(shí)向Repository注入JpqlBeanMethodInterceptor

public class JpqlBeanPostProcessor implements RepositoryProxyPostProcessor {

    @Override
    public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
        factory.addAdvice(new JpqlBeanMethodInterceptor(repositoryInformation));
    }

}

4.自定義JpaRepositoryFactoryBean

創(chuàng)建RepositoryFactory時(shí),向其加入我們自定義的RepositoryProxyPostProcessor

public class GmRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
        extends JpaRepositoryFactoryBean<T, S, ID> {

    public GmRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
        jpaRepositoryFactory.addRepositoryProxyPostProcessor(new JpqlBeanPostProcessor());
        return jpaRepositoryFactory;
    }
}

這一步大家應(yīng)該很熟悉了近上,寫過公用Repository的朋友應(yīng)該都知道其作用剔宪。不過注意一下,我們?cè)诜祷豃paRepositoryFactory前壹无,要將我們的RepositoryProxyPostProcessor 加進(jìn)postProcessors中葱绒,不然前邊就白做了。

5.讓我們的JpaRepositoryFactoryBean起作用

這一步大家應(yīng)該也比較熟悉了斗锭,直接上代碼地淀。

@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = GmRepositoryFactoryBean.class)
@EnableSpringDataWebSupport
public class JpaDataConfig {

}

注意要放到項(xiàng)目目錄下或者在Application把這個(gè)文件的包加到BasePackages中,反正就是要讓Spring Boot啟動(dòng)的時(shí)候掃的到就對(duì)啦岖是。

最后

再次啟動(dòng)單元測(cè)試帮毁,重新執(zhí)行g(shù)etUserInOffice方法实苞,可以看到返回的Page中的content已經(jīng)正常返回User的List了,大功告成烈疚!經(jīng)測(cè)試黔牵,其執(zhí)行速度與原先的返回Object[]基本保持一致,有時(shí)甚至更快胞得。這個(gè)我也不知道為什么會(huì)更快,可能與網(wǎng)絡(luò)因素有關(guān)吧屹电,畢竟我在測(cè)試的時(shí)候數(shù)據(jù)庫不在本地阶剑。。危号。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牧愁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子外莲,更是在濱河造成了極大的恐慌猪半,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偷线,死亡現(xiàn)場(chǎng)離奇詭異磨确,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)声邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門乏奥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亥曹,你說我怎么就攤上這事邓了。” “怎么了媳瞪?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵骗炉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蛇受,道長(zhǎng)句葵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任兢仰,我火速辦了婚禮笼呆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旨别。我一直安慰自己诗赌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布秸弛。 她就那樣靜靜地躺著铭若,像睡著了一般洪碳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叼屠,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天瞳腌,我揣著相機(jī)與錄音,去河邊找鬼镜雨。 笑死嫂侍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荚坞。 我是一名探鬼主播挑宠,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼颓影!你這毒婦竟也來了各淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤诡挂,失蹤者是張志新(化名)和其女友劉穎碎浇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璃俗,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奴璃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了城豁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溺健。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖钮蛛,靈堂內(nèi)的尸體忽然破棺而出鞭缭,到底是詐尸還是另有隱情,我是刑警寧澤魏颓,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布岭辣,位于F島的核電站,受9級(jí)特大地震影響甸饱,放射性物質(zhì)發(fā)生泄漏沦童。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一叹话、第九天 我趴在偏房一處隱蔽的房頂上張望偷遗。 院中可真熱鬧,春花似錦驼壶、人聲如沸氏豌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泵喘。三九已至泪电,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纪铺,已是汗流浹背相速。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲜锚,地道東北人突诬。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芜繁,于是被迫代替她去往敵國和親旺隙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理浆洗,服務(wù)發(fā)現(xiàn)催束,斷路器集峦,智...
    卡卡羅2017閱讀 134,665評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,823評(píng)論 6 342
  • 這部分主要是開源Java EE框架方面的內(nèi)容伏社,包括Hibernate、MyBatis塔淤、Spring摘昌、Spring ...
    雜貨鋪老板閱讀 1,389評(píng)論 0 2
  • 3.1. 核心概念 CrudRepository包含增刪查改基礎(chǔ)功能 PagingAndSortingReposi...
    titvax閱讀 1,757評(píng)論 0 2
  • 飛落的流星,像那次回眸 而幸福 除了無限的追尋 沒什么可以控制那種誘惑 當(dāng)深夜掩蓋了所有光芒 俺必須學(xué)會(huì)修補(bǔ)...
    南山野客閱讀 158評(píng)論 0 1