SpringBoot第八講Spring Data JPA的關(guān)聯(lián)對(duì)象查詢

現(xiàn)在關(guān)于SpringDataJpa我們總算只剩下一個(gè)比較重要的問題了,就是當(dāng)有關(guān)聯(lián)對(duì)象的時(shí)候我們需要把關(guān)聯(lián)的對(duì)象查詢出來,該如何處理,如果在類的關(guān)聯(lián)中是基于對(duì)象關(guān)聯(lián)的,那么Hibernate的抓取策略可以自動(dòng)完成,但是這種操作會(huì)存在各種性能上的問題,所以不建議用關(guān)聯(lián)的方式處理,我們僅僅只是做個(gè)外鍵關(guān)聯(lián),在實(shí)際的應(yīng)用中,我們可以在Student中存儲(chǔ)班級(jí)的冗余來解決,或者直接通過創(chuàng)建DTO對(duì)象來解決,實(shí)際的操作方式和Hibernate原有的操作方式類似,以下我們演示兩種,一種是獲取班級(jí),并且獲得班級(jí)中的學(xué)生信息,另外一種是獲取學(xué)生順便得到學(xué)生對(duì)象。

通過查詢學(xué)生獲取班級(jí)信息

這種需求最簡單的的方式是直接創(chuàng)建StudentDto,并且加入Student和Classroom的關(guān)聯(lián)。如下所示

public class StudentBadDto {

    private Student stu;
    private Classroom cla;
    public StudentBadDto(Student stu, Classroom cla) {
        super();
        this.stu = stu;
        this.cla = cla;
    }

  //省略了getter和setter方法
}

查詢的代碼如下所示:

@Query("select new org.konghao.model.StudentBadDto(stu,cla) from Student stu,Classroom cla where stu.cid=cla.id")
public List<StudentDto> listBadStu();

以上的處理方式和hibernate類似,通過DTO完成查詢,非常的簡單,測試代碼如下:

@Test
public void testListStu() {
    List<StudentDto> sds = studentRepository.listBadStu();
    Assert.assertEquals(5, sds.size());
    Assert.assertEquals(2, sds.get(0).getStu().getId());
    Assert.assertEquals(1, sds.get(0).getCla().getId());
}

以上方法似乎可以滿足要求,但是我們仔細(xì)觀察一下所發(fā)出的sql

springDataJpa的關(guān)聯(lián)查詢
springDataJpa的關(guān)聯(lián)查詢

我們發(fā)現(xiàn)發(fā)出了很多條sql,它先查詢出了student,然后當(dāng)需要Classroom的時(shí)候又發(fā)出了查詢,而且每個(gè)對(duì)象都這樣進(jìn)行了操作,這顯然是我們無法接受的,所以大家記住不能很簡單的用對(duì)象來進(jìn)行處理,我們必須把屬性分開來處理。這其實(shí)是hibernate比較建議的一種操作方式,首先dto中不能有任何的對(duì)象,只能通過屬性來定義。代碼如下:

public class StudentGoodDto {
    private int sid;
    private String sname;
    private int claId;
    private String claName;
    private String grade;

    public StudentGoodDto() {}

    public StudentGoodDto(int sid,String sname, int claId, String claName, String grade) {
        super();
        this.sid = sid;
        this.sname = sname;
        this.claId = claId;
        this.claName = claName;
        this.grade = grade;
    }

    //省略了getter和setter
}

注意以上構(gòu)造函數(shù)的寫法,在具體的Query中需要按照這順序來進(jìn)行構(gòu)造。

@Query("select new org.konghao.model.StudentGoodDto(stu.id,stu.name,cla.id,cla.name,cla.grade) from Student stu,Classroom cla where stu.cid=cla.id")
public List<StudentGoodDto> listGoodStu();

此時(shí)再進(jìn)行調(diào)用之后,我們發(fā)現(xiàn)SQL語句就只剩下一條了

Hibernate: select student0_.id as col_0_0_, student0_.name as col_1_0_, classroom1_.id as col_2_0_, classroom1_.name as col_3_0_, classroom1_.grade as col_4_0_ from t_student student0_ cross join t_classroom classroom1_ where student0_.cid=classroom1_.id

以上就是關(guān)聯(lián)對(duì)象的查詢方法,但是此處比較麻煩的就是以后如果需要查詢學(xué)生對(duì)象,都需要寫如此長的Query查詢,這里可以給大家提供一種思路,就是我們自己創(chuàng)建一個(gè)properties文件,把這個(gè)構(gòu)造函數(shù)通過字符串存儲(chǔ)起來,每次調(diào)用的時(shí)候就自動(dòng)到這個(gè)配置文件中去取,但此時(shí)就無法使用spring data jpa了,需要?jiǎng)?chuàng)建一個(gè)studentRepository來自己實(shí)現(xiàn)。這種方式我就不再實(shí)現(xiàn)了,掌握之后思路比較的簡單。

最后我們來解決一對(duì)多的關(guān)系,一對(duì)多的關(guān)系必須也存儲(chǔ)在dto中。

/**
 * 獲取班級(jí)和具體的學(xué)生列表
 * @author konghao
 *
 */
publ
public class ClassroomDto {
    private int cid;
    private String name;
    private String grade;
    private List<Student> stus;
}

/**
 * 獲取班級(jí)和班級(jí)人數(shù)
 * @author konghao
 *
 */
public class ClassroomStuNumDto {
    private int cid;
    private String cname;
    private String grade;
    private long snum;

    public ClassroomStuNumDto(int cid, String cname, String grade, long snum) {
        super();
        this.cid = cid;
        this.cname = cname;
        this.grade = grade;
        this.snum = snum;
    }
}


我們創(chuàng)建了兩個(gè)dto來模擬兩種狀態(tài):第一個(gè)表示查詢班級(jí)時(shí)得到班級(jí)中的所有學(xué)生信息,第二個(gè)在查詢班級(jí)時(shí)獲取班級(jí)的學(xué)生人數(shù),其實(shí)在實(shí)際的開發(fā)過程中,第一種情況非常的少見,但是有些時(shí)候也會(huì)需要,這里會(huì)給大家提供一種解決方案。但我們在實(shí)際的應(yīng)用中第二查詢估計(jì)會(huì)多一些,實(shí)現(xiàn)第二種查詢的方式很多,一種是在ClassroomRepositoryCustom中自己來實(shí)現(xiàn),,或者直接通過@Query來設(shè)定也可以,以下是實(shí)現(xiàn)代碼:

@Query("select new org.konghao.model.ClassroomStuNumDto(cla.id,cla.name,cla.grade,count(stu.id)) from Classroom cla,Student stu where cla.id=stu.cid group by cla.id")
public List<ClassroomStuNumDto> listClassrooms();

其實(shí)以上方案不是很理想的解決方案,因?yàn)槭褂肏QL在沒有對(duì)象關(guān)聯(lián)的情況下是無法使用left join的,這在做group時(shí)無法統(tǒng)計(jì)得出學(xué)生為0的班級(jí),比較好的解決方案是自己寫這個(gè)實(shí)現(xiàn),通過sql來處理。

第一種需求的代碼如下:

public interface ClassroomRepositoryCustom {

    @Transactional
    public void delete(int cla);

    /**
     * 查詢班級(jí)dto
     * @return
     */
    public List<ClassroomDto> listClassroomDto();
}

首先在ClassroomRepositoryCustom中創(chuàng)建一個(gè)查詢班級(jí)dto的方法,實(shí)現(xiàn)如下:

@Override
public List<ClassroomDto> listClassroomDto() {
    String hql = "select cla.id,cla.name,cla.grade,stu.id,stu.name,stu.age,stu.address from Classroom cla,Student stu where cla.id=stu.cid";
    List<Object[]> list = entityManager.createQuery(hql).getResultList();
    List<ClassroomDto> clas = new ArrayList<ClassroomDto>();
    ClassroomDto cd = null;
    List<Student> stus = null;
    System.out.println(list.size());
    for(Object[] objs:list) {
        Student stu = new Student();
        stu.setId((Integer)objs[3]);
        stu.setName((String)objs[4]);
        stu.setAge((Integer)objs[5]);
        stu.setAddress((String)objs[6]);
        if(!checkExist(clas,(Integer)objs[0])) {
            stus = new ArrayList<Student>();
            stus.add(stu);
            cd = new ClassroomDto();
            cd.setCid((Integer)objs[0]);
            cd.setName((String)objs[1]);
            cd.setGrade((String)objs[2]);
            cd.setStus(stus);
            if(!checkExist(clas, cd.getCid())) clas.add(cd);
        } else {
            cd.getStus().add(stu);
        }
    }
    return clas;
}

private boolean checkExist(List<ClassroomDto> clas, Integer id) {
    for(ClassroomDto cd:clas) {
        if(cd.getCid()==id) return true;
    }
    return false;
}

實(shí)現(xiàn)思路就是把所有的數(shù)據(jù)一次性查詢出來然后進(jìn)行在程序中來進(jìn)行處理。

到此為止我們Spring Data JPA部分的所有知識(shí)都講解完了,我們發(fā)現(xiàn)雖然spring data jpa很好用,但是對(duì)于復(fù)雜的查詢依然還是得自己寫sql或者h(yuǎn)ql來解決,所以很多時(shí)候大家要根據(jù)項(xiàng)目的需求進(jìn)行擴(kuò)展和設(shè)計(jì),在這幾講中已經(jīng)把spring data jpa中比較有用的東西都實(shí)現(xiàn)了,希望對(duì)大家有所幫助,下一塊的知識(shí)給大家講解Thymleaf,這是比較好用的一個(gè)界面層展示模板。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容