現(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

我們發(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è)界面層展示模板。
本文的源代碼在這里:源代碼