SpringBoot第四講擴(kuò)展和封裝Spring Data JPA(一)_自定義Repository和創(chuàng)建自己的BaseRepository

這一講主要介紹Spring Data JPA的封裝。和設(shè)計(jì)相關(guān)的東西都是仁者見仁裤唠,智者見智的事情昔园,如果你有更好的封裝方案可以和我交流炉菲,互相學(xué)習(xí)。這一講會講如下一些內(nèi)容

  • 擴(kuò)展Spring Data JPA實(shí)現(xiàn)自己的一些特殊方法
  • 創(chuàng)建一個(gè)自己的BaseRepository
  • 封裝Specification來快速完成一些簡單的查詢操作
  • 封裝分頁和排序操作补君。

在一些特殊時(shí)候引几,我們會設(shè)計(jì)到對Spring Data JPA中的方法進(jìn)行重新實(shí)現(xiàn),這將會面臨一個(gè)問題赚哗,如果我們新創(chuàng)建一個(gè)實(shí)現(xiàn)類她紫。如果這個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)了JpaRepository接口,這樣我們不得不實(shí)現(xiàn)該接口中的所有方法屿储,如果不實(shí)現(xiàn)該接口贿讹,那意味著我們就無法使用Spring Data JPA中給我們提供的那些好用的方法。所以在擴(kuò)展的時(shí)候我們需要按照如下方法進(jìn)行够掠。

Spring Data JPA 自定義方法

這些需要注意的是民褂,接口和實(shí)現(xiàn)類的名稱必須遵循spring data jpa的命名規(guī)范,如果要為接口StudentBaseRepository寫自定義的接口疯潭,首先需要創(chuàng)建一個(gè)接口名稱為StudentBaseRepositoryCustom赊堪,這表示是自定義接口,實(shí)現(xiàn)類的名稱必須是StudentBaseRepositoryImpl竖哩,此時(shí)當(dāng)StudentBaseRepository實(shí)現(xiàn)StudentBaseRepositoryCustom之后就可以使用我們自己實(shí)現(xiàn)的方法了哭廉,同理StudentBaseRepository也可以繼承JpaRepository來獲取Spring Data Jpa 給我們的方法。

StudentBaseRepositoryCustom代碼如下

public interface StudentBaseRepositoryCustom {
    //基于原生態(tài)的sql進(jìn)行查詢
    List<Object[]> groupByStudentAsSql();
    //基于Hibernate的HQL進(jìn)行查詢
    List<Object[]> groupByStudentAsHql();
    //基于Specification的方式進(jìn)行查詢相叁,使用的是CriteriaQuery進(jìn)行查詢
    List<Object[]> groupByStudentAsSpecification();
}

以上代碼中定義了三個(gè)方法遵绰,第一個(gè)是基于原始的SQL來進(jìn)行分組查詢辽幌,第二個(gè)是基于Hibernate的HQL進(jìn)行查詢,最后一個(gè)是用Specification中的CriteriaQuery來進(jìn)行處理椿访,首先要解決的問題是StudentBaseRepositoryCustom沒有實(shí)現(xiàn)Repository乌企,該如何來執(zhí)行SQL語句呢,我們可以給實(shí)現(xiàn)類注入另一個(gè)EntityManger成玫,通過EntityManager來執(zhí)行SQL語句加酵。以下是StudentBaseRepositoryImpl的實(shí)現(xiàn)代碼

public class StudentBaseRepositoryImpl implements StudentBaseRepositoryCustom {
    @Autowired
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<Object[]> groupByStudentAsSql() {
        List<Object[]> list = entityManager
                .createNativeQuery("select address,count(*) from t_student group by address")
                .getResultList();

        return list;
    }

    @Override
    public List<Object[]> groupByStudentAsHql() {
        List<Object[]> list = entityManager
                .createQuery("select address,count(*) from Student group by address")
                .getResultList();
        return list;
    }

    @Override
    public List<Object[]> groupByStudentAsSpecification() {
        //根據(jù)地址分組查詢,并且學(xué)生數(shù)量大于3的所有地址
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
        Root<Student> root = query.from(Student.class);
        query.multiselect(root.get("address"),builder.count(root.get("id")))
                .groupBy(root.get("address")).having(builder.gt(builder.count(root.get("id")),3));

        return entityManager.createQuery(query).getResultList();
    }
}

前面兩個(gè)方法的實(shí)現(xiàn)都非常容易理解哭当,就是創(chuàng)建一個(gè)查詢語句猪腕,執(zhí)行完成之后會返回一組Object[]的投影,第三個(gè)方法稍微有些復(fù)雜荣病,這是CriteriaQuery的標(biāo)準(zhǔn)寫法码撰。

到這里我們解決了擴(kuò)展類的問題,但仍然有些疑問个盆,如果每個(gè)類都有自己獨(dú)立的方法脖岛,那么是不是每一個(gè)類都得按照上面的方面來寫接口和實(shí)現(xiàn)類,以上做法雖然可以很好的解決自定義類的擴(kuò)展問題颊亮,但是仍然稍顯麻煩柴梆,我們可以定義一個(gè)基類來覆蓋一些比較通用的方法,如通用的SQL查詢等终惑。下面我們就來創(chuàng)建這個(gè)BaseRepository绍在,整個(gè)創(chuàng)建的過程有些復(fù)雜,可以參照項(xiàng)目的源代碼(源代碼在文章的最后有鏈接)雹有。

創(chuàng)建的第一步定義一個(gè)BaseRepository的接口

package org.konghao.repo.base;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;
import java.util.List;

/**
 * Created by konghao on 2016/12/7.
 */
@NoRepositoryBean
public interface BaseRepository<T,ID extends Serializable> extends JpaRepository<T,ID> {
    List<Object[]> listBySQL(String sql);
}

該接口實(shí)現(xiàn)了JpaRepository,這樣就保證擁有了Spring Data JPA中那些比較好用的方法霸奕,然后可以自定義自己需要的方法溜宽,代碼中定義了一個(gè)listBySQL的方法。需要注意的是@NoRepositoryBean质帅,這個(gè)表示該接口不會創(chuàng)建這個(gè)接口的實(shí)例(我們原來定義的StudentPageRepository這些适揉,Spring Data JPA的基礎(chǔ)組件都會自動為我們創(chuàng)建一個(gè)實(shí)例對象,加上這個(gè)annotation煤惩,spring data jpa的基礎(chǔ)組件就不會再為我們創(chuàng)建它的實(shí)例)嫉嘀。之后我們編寫實(shí)現(xiàn)類BaseRepositoryImpl

package org.konghao.repo.base;

import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.List;

/**
 * Created by konghao on 2016/12/7.
 */
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T,ID>
        implements BaseRepository<T,ID> {

    private final EntityManager entityManager;

    //父類沒有不帶參數(shù)的構(gòu)造方法,這里手動構(gòu)造父類
    public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    //通過EntityManager來完成查詢
    @Override
    public List<Object[]> listBySQL(String sql) {
        return entityManager.createNativeQuery(sql).getResultList();
    }
}

這個(gè)實(shí)現(xiàn)類比較的簡單魄揉,首先我們需要繼承SimpleJpaRepository剪侮,SimpleJpaRepository幫助我們實(shí)現(xiàn)了JpaRepository中的方法。然后實(shí)現(xiàn)BaseRepository接口洛退。listBySQL方法非常的簡單瓣俯,上面已經(jīng)詳細(xì)介紹過了红淡,具體的作用就是執(zhí)行一條sql返回一組投影的列表。

下一步我們需要創(chuàng)建一個(gè)自定義的工廠降铸,在這個(gè)工廠中注冊我們自己定義的BaseRepositoryImpl的實(shí)現(xiàn)。這個(gè)工廠的寫法具體參照Spring Data的JpaRepositoryFactoryBeanJpaRepositoryFactory摇零。這個(gè)類上面一堆的泛型推掸,我們不用考慮,只要按照相同的方式來寫即可驻仅。

創(chuàng)建JpaRepositoryFactoryBean需要調(diào)用如下方法谅畅,通過這個(gè)方法來返回一個(gè)工廠,這里返回的是JpaRepositoryFactory噪服。

protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
    return new JpaRepositoryFactory(entityManager);
}

在JpaRepositoryFactory中毡泻,有兩個(gè)方法比較關(guān)鍵

protected Object getTargetRepository(RepositoryInformation information) {
    SimpleJpaRepository repository = this.getTargetRepository(information, this.entityManager);
    repository.setRepositoryMethodMetadata(this.crudMethodMetadataPostProcessor.getCrudMethodMetadata());
    return repository;
}

protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
   return this.isQueryDslExecutor(metadata.getRepositoryInterface())?QueryDslJpaRepository.class:SimpleJpaRepository.class;
}

通過這兩個(gè)方法來確定具體的實(shí)現(xiàn)類,也就是Spring Data Jpa具體實(shí)例化一個(gè)接口的時(shí)候會去創(chuàng)建的實(shí)現(xiàn)類粘优。通過代碼我們可以發(fā)現(xiàn)仇味,Spring Data JPA都是調(diào)用SimpleJpaRepository來創(chuàng)建實(shí)例。以下是我們自己的工廠實(shí)現(xiàn)的代碼

package org.konghao.repo.base;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

/**
 * Created by konghao on 2016/12/7.
 */
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
        I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
        return new BaseRepositoryFactory(em);
    }

    //創(chuàng)建一個(gè)內(nèi)部類雹顺,該類不用在外部訪問
    private static class BaseRepositoryFactory<T, I extends Serializable>
            extends JpaRepositoryFactory {

        private final EntityManager em;

        public BaseRepositoryFactory(EntityManager em) {
            super(em);
            this.em = em;
        }

        //設(shè)置具體的實(shí)現(xiàn)類是BaseRepositoryImpl
        @Override
        protected Object getTargetRepository(RepositoryInformation information) {
            return new BaseRepositoryImpl<T, I>((Class<T>) information.getDomainType(), em);
        }

        //設(shè)置具體的實(shí)現(xiàn)類的class
        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return BaseRepositoryImpl.class;
        }
    }
}

接著我們需要讓spring在加載的時(shí)候找到我們自定義的BaseRepository的工廠丹墨,當(dāng)我們使用了SpringBoot之后一切都變得簡單了,只要在入口類中加入@EnableJpaRepositories即可嬉愧,代碼如下

/**
 * Created by konghao on 2016/11/24.
 */
 @EnableJpaRepositories(basePackages = {"org.konghao"},
         repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class//指定自己的工廠類
 )
 @SpringBootApplication
 public class DemoApplication {
     public static void main(String[] args) {
         SpringApplication.run(DemoApplication.class,args);
     }
 }

到這里我們的整個(gè)自定義工廠的流程就結(jié)束了贩挣,我們寫一個(gè)接口實(shí)現(xiàn)BaseRepository即可

/**
 * Created by konghao on 2016/12/7.
 */
public interface StudentExtendsRepository extends BaseRepository<Student,Integer> {
    /**
     * 原來JPARepository的方法依然可以使用*/
    List<Student> findByNameAndAddress(String name, String address);
}

測試類中的代碼如下

@Test
public void testBaseRepository() {
    //直接使用BaseRepository中的方法
    List<Object[]> list = studentExtendsRepository.listBySQL("select address,count(*) from t_student group by address");
    Assert.assertEquals(2,list.size());
    Assert.assertEquals("km",list.get(0)[0]);
    //原JpaRepository的方法依然可以使用
    List<Student> list2 = studentExtendsRepository.findByNameAndAddress("bar","zt");
    Assert.assertEquals(1,list2.size());
}

到這里,我們這部分的內(nèi)容就基本結(jié)束了没酣,下一章節(jié)我們要解決的問題是基于Specification的封裝問題王财。

本文的源代碼在這里:源代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市裕便,隨后出現(xiàn)的幾起案子绒净,更是在濱河造成了極大的恐慌,老刑警劉巖闪金,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疯溺,死亡現(xiàn)場離奇詭異,居然都是意外死亡哎垦,警方通過查閱死者的電腦和手機(jī)囱嫩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漏设,“玉大人墨闲,你說我怎么就攤上這事≈?冢” “怎么了鸳碧?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵盾鳞,是天一觀的道長。 經(jīng)常有香客問我瞻离,道長腾仅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任套利,我火速辦了婚禮推励,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肉迫。我一直安慰自己验辞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布喊衫。 她就那樣靜靜地躺著跌造,像睡著了一般。 火紅的嫁衣襯著肌膚如雪族购。 梳的紋絲不亂的頭發(fā)上壳贪,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機(jī)與錄音寝杖,去河邊找鬼撑碴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛朝墩,可吹牛的內(nèi)容都是我干的醉拓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼收苏,長吁一口氣:“原來是場噩夢啊……” “哼亿卤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鹿霸,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤排吴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后懦鼠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钻哩,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年肛冶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了街氢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睦袖,死狀恐怖珊肃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤伦乔,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布厉亏,位于F島的核電站,受9級特大地震影響烈和,放射性物質(zhì)發(fā)生泄漏爱只。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一招刹、第九天 我趴在偏房一處隱蔽的房頂上張望虱颗。 院中可真熱鬧,春花似錦蔗喂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至散址,卻和暖如春乖阵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背预麸。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工瞪浸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吏祸。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓对蒲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贡翘。 傳聞我的和親對象是個(gè)殘疾皇子蹈矮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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