現(xiàn)在關于SpringDataJpa我們總算只剩下一個比較重要的問題了,就是當有關聯(lián)對象的時候我們需要把關聯(lián)的對象查詢出來,該如何處理,如果在類的關聯(lián)中是基于對象關聯(lián)的,那么Hibernate的抓取策略可以自動完成,但是這種操作會存在各種性能上的問題,所以不建議用關聯(lián)的方式處理,我們僅僅只是做個外鍵關聯(lián),在實際的應用中,我們可以在Student中存儲班級的冗余來解決,或者直接通過創(chuàng)建DTO對象來解決,實際的操作方式和Hibernate原有的操作方式類似,以下我們演示兩種,一種是獲取班級,并且獲得班級中的學生信息,另外一種是獲取學生順便得到學生對象。
通過查詢學生獲取班級信息
這種需求最簡單的的方式是直接創(chuàng)建StudentDto,并且加入Student和Classroom的關聯(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());
}
以上方法似乎可以滿足要求,但是我們仔細觀察一下所發(fā)出的sql

我們發(fā)現(xiàn)發(fā)出了很多條sql,它先查詢出了student,然后當需要Classroom的時候又發(fā)出了查詢,而且每個對象都這樣進行了操作,這顯然是我們無法接受的,所以大家記住不能很簡單的用對象來進行處理,我們必須把屬性分開來處理。這其實是hibernate比較建議的一種操作方式,首先dto中不能有任何的對象,只能通過屬性來定義。代碼如下:
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
}
注意以上構造函數(shù)的寫法,在具體的Query中需要按照這順序來進行構造。
@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();
此時再進行調用之后,我們發(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
以上就是關聯(lián)對象的查詢方法,但是此處比較麻煩的就是以后如果需要查詢學生對象,都需要寫如此長的Query查詢,這里可以給大家提供一種思路,就是我們自己創(chuàng)建一個properties文件,把這個構造函數(shù)通過字符串存儲起來,每次調用的時候就自動到這個配置文件中去取,但此時就無法使用spring data jpa了,需要創(chuàng)建一個studentRepository來自己實現(xiàn)。這種方式我就不再實現(xiàn)了,掌握之后思路比較的簡單。
最后我們來解決一對多的關系,一對多的關系必須也存儲在dto中。
/**
* 獲取班級和具體的學生列表
* @author konghao
*
*/
publ
public class ClassroomDto {
private int cid;
private String name;
private String grade;
private List<Student> stus;
}
/**
* 獲取班級和班級人數(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)建了兩個dto來模擬兩種狀態(tài):第一個表示查詢班級時得到班級中的所有學生信息,第二個在查詢班級時獲取班級的學生人數(shù),其實在實際的開發(fā)過程中,第一種情況非常的少見,但是有些時候也會需要,這里會給大家提供一種解決方案。但我們在實際的應用中第二查詢估計會多一些,實現(xiàn)第二種查詢的方式很多,一種是在ClassroomRepositoryCustom中自己來實現(xiàn),,或者直接通過@Query來設定也可以,以下是實現(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();
其實以上方案不是很理想的解決方案,因為使用HQL在沒有對象關聯(lián)的情況下是無法使用left join的,這在做group時無法統(tǒng)計得出學生為0的班級,比較好的解決方案是自己寫這個實現(xiàn),通過sql來處理。
第一種需求的代碼如下:
public interface ClassroomRepositoryCustom {
@Transactional
public void delete(int cla);
/**
* 查詢班級dto
* @return
*/
public List<ClassroomDto> listClassroomDto();
}
首先在ClassroomRepositoryCustom中創(chuàng)建一個查詢班級dto的方法,實現(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;
}
實現(xiàn)思路就是把所有的數(shù)據(jù)一次性查詢出來然后進行在程序中來進行處理。
到此為止我們Spring Data JPA部分的所有知識都講解完了,我們發(fā)現(xiàn)雖然spring data jpa很好用,但是對于復雜的查詢依然還是得自己寫sql或者hql來解決,所以很多時候大家要根據(jù)項目的需求進行擴展和設計,在這幾講中已經(jīng)把spring data jpa中比較有用的東西都實現(xiàn)了,希望對大家有所幫助,下一塊的知識給大家講解Thymleaf,這是比較好用的一個界面層展示模板。
本文的源代碼在這里:源代碼